zischenstand
This commit is contained in:
21
src/modules/objects-interactions-fx/LICENSE
Normal file
21
src/modules/objects-interactions-fx/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Zoty
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
71
src/modules/objects-interactions-fx/module.json
Normal file
71
src/modules/objects-interactions-fx/module.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"id": "objects-interactions-fx",
|
||||
"title": "✨ Automated Objects, Interactions and Effects 🔥",
|
||||
"description": "Animations. Automations. Interactions. Effects. And soon Objects too! Immerse your players on the game, let they experience the interactions and feel the combat!",
|
||||
"version": "1.1.5",
|
||||
"authors": [
|
||||
{
|
||||
"name": "ZotyDev",
|
||||
"email": "official@zoty.dev",
|
||||
"url": "https://www.zoty.dev",
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"lang": "en",
|
||||
"name": "English",
|
||||
"path": "./module/lang/en.json",
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"compatibility": {
|
||||
"minimun": "10",
|
||||
"verified": "11"
|
||||
},
|
||||
"license": "./LICENSE",
|
||||
"url": "https://github.com/ZotyDev/objects-interactions-fx",
|
||||
"bugs": "https://github.com/ZotyDev/objects-interactions-fx/issues",
|
||||
"manifest": "https://github.com/ZotyDev/objects-interactions-fx/releases/latest/download/module.json",
|
||||
"download": "https://github.com/ZotyDev/objects-interactions-fx/releases/download/1.1.5/module.zip",
|
||||
"readme": "https://github.com/ZotyDev/objects-interactions-fx/blob/main/README.md",
|
||||
"changelog": "https://github.com/ZotyDev/objects-interactions-fx/blob/main/CHANGELOG.md",
|
||||
"relationships": {
|
||||
"systems": [
|
||||
{
|
||||
"id": "dnd5e",
|
||||
"type": "system",
|
||||
"compatibility": {}
|
||||
}
|
||||
],
|
||||
"requires": [
|
||||
{
|
||||
"id": "item-tags",
|
||||
"type": "module",
|
||||
"compatibility": {}
|
||||
},
|
||||
{
|
||||
"id": "sequencer",
|
||||
"type": "module",
|
||||
"compatibility": {}
|
||||
},
|
||||
{
|
||||
"id": "socketlib",
|
||||
"type": "module",
|
||||
"compatibility": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"socket": true,
|
||||
"esmodules": [
|
||||
"./module/scripts/Main.js",
|
||||
"./module/scripts/automation/ActorInventor.js",
|
||||
"./module/scripts/library/CanvasEffects.js",
|
||||
"./module/scripts/library/Socket.js",
|
||||
"./module/scripts/library/HandlebarsHelpers.js"
|
||||
],
|
||||
"styles": [
|
||||
"./module/styles/ItemTags.css",
|
||||
"./module/styles/Settings.css"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "Empty",
|
||||
"name": "Empty",
|
||||
"default": "true",
|
||||
"tags": {
|
||||
"powerful": {
|
||||
"type": "special",
|
||||
"enabled": true
|
||||
},
|
||||
"generateCurrency": {
|
||||
"type": "special",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
{
|
||||
"id": "DefaultFantasyTagsJB2AComplete",
|
||||
"name": "Default Packs: Fantasy (JB2A Complete)",
|
||||
"requires": [
|
||||
"jb2a_patreon"
|
||||
],
|
||||
"default": true,
|
||||
"tags": {
|
||||
"powerful": {
|
||||
"type": "special",
|
||||
"enabled": true
|
||||
},
|
||||
"generateCurrency": {
|
||||
"type": "special",
|
||||
"enabled": true
|
||||
},
|
||||
"club": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.club.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"dagger": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.dagger.melee.fire.white",
|
||||
"delay": 950
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.dagger.throw.01.white",
|
||||
"delay": 950
|
||||
},
|
||||
"returnAnimation": {
|
||||
"source": "jb2a.dagger.return.01.white",
|
||||
"delay": 900
|
||||
}
|
||||
},
|
||||
"falchion": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.falchion.melee.01.white",
|
||||
"delay": 1150
|
||||
}
|
||||
},
|
||||
"glaive": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.glaive.melee.01.white",
|
||||
"delay": 1150
|
||||
}
|
||||
},
|
||||
"greataxe": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.greataxe.melee.standard.white",
|
||||
"delay": 1700
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.greataxe.throw.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"greatclub": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.greatclub.standard.white",
|
||||
"delay": 1100
|
||||
}
|
||||
},
|
||||
"greatsword": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.greatsword.melee.standard.white",
|
||||
"delay": 1700
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.greatsword.throw",
|
||||
"delay": 1250
|
||||
},
|
||||
"returnAnimation": {
|
||||
"source": "jb2a.greatsword.return",
|
||||
"delay": 800
|
||||
}
|
||||
},
|
||||
"halberd": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.halberd.melee.01.white",
|
||||
"delay": 1150
|
||||
}
|
||||
},
|
||||
"hammer": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.hammer.melee.01.white",
|
||||
"delay": 1150
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.hammer.throw",
|
||||
"delay": 900
|
||||
},
|
||||
"returnAnimation": {
|
||||
"source": "jb2a.hammer.return",
|
||||
"delay": 950
|
||||
}
|
||||
},
|
||||
"javelin": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.spear.melee.01.white.2",
|
||||
"delay": 1250
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.javelin.01.throw",
|
||||
"delay": 1000
|
||||
},
|
||||
"returnAnimation": {
|
||||
"source": "jb2a.javelin.01.return",
|
||||
"delay": 800
|
||||
}
|
||||
},
|
||||
"kunai": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.dagger.melee.fire.white",
|
||||
"delay": 950
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.kunai.throw.01",
|
||||
"delay": 900
|
||||
}
|
||||
},
|
||||
"handaxe": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.handaxe.melee.standard.white",
|
||||
"delay": 1150
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.handaxe.throw.01",
|
||||
"delay": 900
|
||||
}
|
||||
},
|
||||
"mace": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.mace.melee.01.white",
|
||||
"delay": 1200
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.mace.throw",
|
||||
"delay": 900
|
||||
}
|
||||
},
|
||||
"maul": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.maul.melee.standard.white",
|
||||
"delay": 1900,
|
||||
"powerful": true
|
||||
}
|
||||
},
|
||||
"quarterstaff": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.quarterstaff.melee.01.white",
|
||||
"delay": 900
|
||||
}
|
||||
},
|
||||
"rapier": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.rapier.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"scimitar": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.scimitar.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"shortsword": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.shortsword.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"spear": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.spear.melee.01.white",
|
||||
"delay": 1250
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.spear.throw.01",
|
||||
"delay": 950
|
||||
},
|
||||
"returnAnimation": {
|
||||
"source": "jb2a.spear.return.01",
|
||||
"delay": 850
|
||||
}
|
||||
},
|
||||
"sword": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.sword.melee.01.white",
|
||||
"delay": 1250
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.sword.throw.white",
|
||||
"delay": 1150
|
||||
}
|
||||
},
|
||||
"warhammer": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.warhammer.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"wrench": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.wrench.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"shortbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true,
|
||||
"rangedAnimation": {
|
||||
"source": "jb2a.arrow.physical.white.01",
|
||||
"delay": 600
|
||||
}
|
||||
},
|
||||
"longbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true,
|
||||
"rangedAnimation": {
|
||||
"source": "jb2a.arrow.physical.white.01",
|
||||
"delay": 600
|
||||
}
|
||||
},
|
||||
"handcrossbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true,
|
||||
"rangedAnimation": {
|
||||
"source": "jb2a.bolt.physical.white02",
|
||||
"delay": 600
|
||||
}
|
||||
},
|
||||
"lightcrossbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true,
|
||||
"rangedAnimation": {
|
||||
"source": "jb2a.bolt.physical.white02",
|
||||
"delay": 600
|
||||
}
|
||||
},
|
||||
"heavycrossbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true,
|
||||
"rangedAnimation": {
|
||||
"source": "jb2a.bolt.physical.white02",
|
||||
"delay": 600
|
||||
}
|
||||
},
|
||||
"torch": {
|
||||
"type": "lighting",
|
||||
"enabled": true,
|
||||
"light": {
|
||||
"animationType": "torch",
|
||||
"animationSpeed": 5,
|
||||
"animationIntensity": 5,
|
||||
"animationReverse": false,
|
||||
"color": "#ffae00",
|
||||
"intensity": 0.4,
|
||||
"angle": 360
|
||||
},
|
||||
"icons": {
|
||||
"unlit": "icons/sundries/lights/torch-black.webp",
|
||||
"lit": "icons/sundries/lights/torch-brown-lit.webp"
|
||||
}
|
||||
},
|
||||
"lamp": {
|
||||
"type": "lighting",
|
||||
"enabled": true,
|
||||
"light": {
|
||||
"animationType": "torch",
|
||||
"animationSpeed": 5,
|
||||
"animationIntensity": 5,
|
||||
"animationReverse": false,
|
||||
"color": "#ffa500",
|
||||
"intensity": 0.5,
|
||||
"angle": 360
|
||||
},
|
||||
"icons": {
|
||||
"unlit": "icons/sundries/lights/lantern-steel.webp",
|
||||
"lit": "icons/sundries/lights/lantern-iron-lit-yellow.webp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"id": "DefaultFantasyTagsJB2AFree",
|
||||
"name": "Default Packs: Fantasy (JB2A Free)",
|
||||
"requires": [
|
||||
"JB2A_DnD5e"
|
||||
],
|
||||
"default": true,
|
||||
"tags": {
|
||||
"powerful": {
|
||||
"type": "special",
|
||||
"enabled": true
|
||||
},
|
||||
"generateCurrency": {
|
||||
"type": "special",
|
||||
"enabled": true
|
||||
},
|
||||
"club": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.club.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"dagger": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.dagger.melee.fire.white",
|
||||
"delay": 950
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.dagger.throw.01.white",
|
||||
"delay": 950
|
||||
},
|
||||
"returnAnimation": {
|
||||
"source": "jb2a.dagger.return.01.white",
|
||||
"delay": 900
|
||||
}
|
||||
},
|
||||
"falchion": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.falchion.melee.01.white",
|
||||
"delay": 1150
|
||||
}
|
||||
},
|
||||
"glaive": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.glaive.melee.01.white",
|
||||
"delay": 1150
|
||||
}
|
||||
},
|
||||
"greataxe": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.greataxe.melee.standard.white",
|
||||
"delay": 1700
|
||||
},
|
||||
"throwAnimation": {
|
||||
"source": "jb2a.greataxe.throw.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"greatclub": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.greatclub.standard.white",
|
||||
"delay": 1100
|
||||
}
|
||||
},
|
||||
"greatsword": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.greatsword.melee.standard.white",
|
||||
"delay": 1700
|
||||
}
|
||||
},
|
||||
"halberd": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.halberd.melee.01.white",
|
||||
"delay": 1150
|
||||
}
|
||||
},
|
||||
"hammer": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.hammer.melee.01.white",
|
||||
"delay": 1150
|
||||
}
|
||||
},
|
||||
"javelin": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.spear.melee.01.white.2",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"handaxe": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.handaxe.melee.standard.white",
|
||||
"delay": 1150
|
||||
}
|
||||
},
|
||||
"mace": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.mace.melee.01.white",
|
||||
"delay": 1200
|
||||
}
|
||||
},
|
||||
"maul": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.maul.melee.standard.white",
|
||||
"delay": 1900
|
||||
}
|
||||
},
|
||||
"quarterstaff": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.quarterstaff.melee.01.white",
|
||||
"delay": 900
|
||||
}
|
||||
},
|
||||
"rapier": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.rapier.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"scimitar": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.scimitar.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"shortsword": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.shortsword.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"spear": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.spear.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"sword": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.sword.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"warhammer": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.warhammer.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"wrench": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true,
|
||||
"meleeAnimation": {
|
||||
"source": "jb2a.wrench.melee.01.white",
|
||||
"delay": 1250
|
||||
}
|
||||
},
|
||||
"shortbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true,
|
||||
"rangedAnimation": {
|
||||
"source": "jb2a.arrow.physical.white.01",
|
||||
"delay": 600
|
||||
}
|
||||
},
|
||||
"longbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true,
|
||||
"rangedAnimation": {
|
||||
"source": "jb2a.arrow.physical.white.01",
|
||||
"delay": 600
|
||||
}
|
||||
},
|
||||
"torch": {
|
||||
"type": "lighting",
|
||||
"enabled": true,
|
||||
"ligth": {
|
||||
"animationType": "torch",
|
||||
"color": "#ffae00",
|
||||
"alpha": 0.4
|
||||
},
|
||||
"icons": {
|
||||
"unlit": "icons/sundries/lights/torch-black.webp",
|
||||
"lit": "icons/sundries/lights/torch-brown-lit.webp"
|
||||
}
|
||||
},
|
||||
"lamp": {
|
||||
"type": "lighting",
|
||||
"enabled": true,
|
||||
"light": {
|
||||
"animationType": "torch",
|
||||
"color": "#ffa500",
|
||||
"alpha": 0.5
|
||||
},
|
||||
"icons": {
|
||||
"unlit": "icons/sundries/lights/lantern-steel.webp",
|
||||
"lit": "icons/sundries/lights/lantern-iron-lit-yellow.webp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"id": "DefaultFantasyTagsNoAnimations",
|
||||
"name": "Default Packs: Fantasy (No Animation)",
|
||||
"default": true,
|
||||
"tags": {
|
||||
"powerful": {
|
||||
"type": "special",
|
||||
"enabled": true
|
||||
},
|
||||
"generateCurrency": {
|
||||
"type": "special",
|
||||
"enabled": true
|
||||
},
|
||||
"club": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"dagger": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"falchion": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"glaive": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"greataxe": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"greatclub": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"greatsword": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"halberd": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"hammer": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"javelin": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"kunai": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"handaxe": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"mace": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"maul": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"quarterstaff": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"rapier": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"scimitar": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"shortsword": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"spear": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"sword": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"warhammer": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"wrench": {
|
||||
"type": "meleeAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"shortbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"longbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"handcrossbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"lightcrossbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"heavycrossbow": {
|
||||
"type": "rangedAttack",
|
||||
"enabled": true
|
||||
},
|
||||
"torch": {
|
||||
"type": "lighting",
|
||||
"enabled": true,
|
||||
"light": {
|
||||
"animationType": "torch",
|
||||
"animationSpeed": 5,
|
||||
"animationIntensity": 5,
|
||||
"animationReverse": false,
|
||||
"color": "#ffae00",
|
||||
"intensity": 0.4,
|
||||
"angle": 360
|
||||
},
|
||||
"icons": {
|
||||
"unlit": "icons/sundries/lights/torch-black.webp",
|
||||
"lit": "icons/sundries/lights/torch-brown-lit.webp"
|
||||
}
|
||||
},
|
||||
"lamp": {
|
||||
"type": "lighting",
|
||||
"enabled": true,
|
||||
"light": {
|
||||
"animationType": "torch",
|
||||
"animationSpeed": 5,
|
||||
"animationIntensity": 5,
|
||||
"animationReverse": false,
|
||||
"color": "#ffa500",
|
||||
"intensity": 0.5,
|
||||
"angle": 360
|
||||
},
|
||||
"icons": {
|
||||
"unlit": "icons/sundries/lights/lantern-steel.webp",
|
||||
"lit": "icons/sundries/lights/lantern-iron-lit-yellow.webp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
430
src/modules/objects-interactions-fx/module/lang/en.json
Normal file
430
src/modules/objects-interactions-fx/module/lang/en.json
Normal file
@@ -0,0 +1,430 @@
|
||||
{
|
||||
"OIF": {
|
||||
"Core": {
|
||||
"MissingRequiredModule": "Automated Objects, Interactions and Effects | The required module \"$module\" is required but is currently not installed and/or enabled!"
|
||||
},
|
||||
"ItemTags": {
|
||||
"Text": "Tags:",
|
||||
"Save": "Save"
|
||||
},
|
||||
"Attack": {
|
||||
"Melee": {
|
||||
"Error": {
|
||||
"NotEnough": "You have no more of this item!",
|
||||
"TooFar": "Attack outside of maximun range!"
|
||||
}
|
||||
},
|
||||
"Ranged": {
|
||||
"Error": {
|
||||
"NoAmmo": "You have no ammunition selected!",
|
||||
"TooFar": "Attack outside of maximum range!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Item": {
|
||||
"Lighting": {
|
||||
"Error": {
|
||||
"AlreadySource": "There is another item already providing light",
|
||||
"NotSource": "There is no item providing light",
|
||||
"NotRightSource": "This item is not the one currently providing light"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tooltips": {
|
||||
"MasterTags": {
|
||||
"Title": "Master Tags"
|
||||
},
|
||||
"ClearLighting": {
|
||||
"Title": "Clear Lighting"
|
||||
},
|
||||
"Configuration": {
|
||||
"Title": "Configuration"
|
||||
}
|
||||
},
|
||||
"Settings": {
|
||||
"AttachHooks": {
|
||||
"Attack": {
|
||||
"DnD5eAfterAttackRoll": {
|
||||
"Label": "DnD5e After JUST Attack Roll"
|
||||
},
|
||||
"DnD5eAfterDamageRoll": {
|
||||
"Label": "DnD5e After JUST Damage Roll"
|
||||
},
|
||||
"MidiQOLAfterAttackRoll": {
|
||||
"Label": "MidiQOL After JUST Attack Roll",
|
||||
"DisabledMessage": "- Requires MidiQOL"
|
||||
},
|
||||
"MidiQOLAfterDamageRoll": {
|
||||
"Label": "MidiQOL After JUST Damage Roll",
|
||||
"DisabledMessage": "- Requires MidiQOL"
|
||||
},
|
||||
"MidiQOLAfterCompleteRoll": {
|
||||
"Label": "MidiQOL After Complete Roll",
|
||||
"DisabledMessage": "- Requires MidiQOL"
|
||||
},
|
||||
"Pf2eAfterAttackRoll": {
|
||||
"Label": "Pf2e After JUST Attack Roll"
|
||||
}
|
||||
},
|
||||
"Item": {
|
||||
"DnD5AfterItemRoll": {
|
||||
"Label": "DnD5e After Item Roll"
|
||||
},
|
||||
"Pf2eAfterItemRoll": {
|
||||
"Label": "Pf2e After Item Roll"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MasterTagsSettings": {
|
||||
"Title": "Master Tags Settings",
|
||||
"Hint": "Edit Master Tags and the interactions related to them.",
|
||||
"Label": "Master Tags",
|
||||
"Load": {
|
||||
"Error": {
|
||||
"NotFound": "The ${pack} pack was not found"
|
||||
}
|
||||
},
|
||||
"Creation": {
|
||||
"Error": {
|
||||
"Duplicated": "This tag is already defined"
|
||||
}
|
||||
},
|
||||
"Deletion": {
|
||||
"Title": "Delete \"${tag}\" tag?",
|
||||
"Description": "There is no way to recover a deleted tag, if you delete it by accident you only chance of getting it back is by accessing the JSON file before saving the modifications",
|
||||
"PendingChanges": "You have pending changes, please save or discard them before deleting a new pack"
|
||||
},
|
||||
"Pack": {
|
||||
"Title": "Tag Pack",
|
||||
"Select": "Select Tag Pack:",
|
||||
"DisabledMessage": "- Requires \"${modules}\"",
|
||||
"Delete": {
|
||||
"Label": "Delete",
|
||||
"Description": "Are you sure you want to delete this pack? This action cannot be undone",
|
||||
"Title": "Delete ${pack}?",
|
||||
"Confirm": "Ok",
|
||||
"Cancel": "Ok"
|
||||
},
|
||||
"PendingChanges": "You have pending changes, do you want to discard them?",
|
||||
"Rename": {
|
||||
"Label": "Rename",
|
||||
"Title": "Rename Pack",
|
||||
"Id": "New Pack Id (ONLY letters and numbers, no spaces and etc)",
|
||||
"Name": "New Pack Name",
|
||||
"Error": {
|
||||
"EmptyValues": "The new pack id and name cannot be empty",
|
||||
"BothAlreadyExist": "The pack \"${pack}\" already exists and the new pack id \"${id}\" is already in use",
|
||||
"IDAlreadyExist": "The new pack id \"${id}\" is already in use",
|
||||
"NameAlreadyExist": "The pack \"${pack}\" already exists"
|
||||
}
|
||||
},
|
||||
"Export": "Export",
|
||||
"Import": {
|
||||
"Title": "Import",
|
||||
"Error": {
|
||||
"DuplicatedID": "The ID \"${id}\" is already in use",
|
||||
"DuplicatedName": "The name \"${name}\" is already in use"
|
||||
},
|
||||
"Duplicated": {
|
||||
"Title": "Duplicated Pack",
|
||||
"Description": "The pack \"${pack}\" already exists. Do you want to replace it?"
|
||||
}
|
||||
},
|
||||
"Copy": "Copy",
|
||||
"Create": "Create",
|
||||
"Save": "Save"
|
||||
},
|
||||
"NewPackCopy": {
|
||||
"Title": "New Pack",
|
||||
"Description": "You cannot save changes at default packs, but you can create a new one containing the changes you just made.",
|
||||
"Id": "New Pack Id (ONLY letters and numbers, no spaces and etc)",
|
||||
"Name": "New Pack Name",
|
||||
"Create": "Create",
|
||||
"Copy": "Copy \"${pack}\"",
|
||||
"Error": {
|
||||
"BothAlreadyExist": "The pack \"${pack}\" already exists and the new pack id \"${id}\" is already in use",
|
||||
"IDAlreadyExist": "The new pack id \"${id}\" is already in use",
|
||||
"NameAlreadyExist": "The pack \"${pack}\" already exists"
|
||||
}
|
||||
},
|
||||
"NewPack": {
|
||||
"Title": "New Pack",
|
||||
"Description": "Create a new pack from scratch with no Tags.",
|
||||
"Id": "New Pack Id (ONLY letters and numbers, no spaces and etc)",
|
||||
"Name": "New Pack Name",
|
||||
"Create": "Create",
|
||||
"Copy": "Copy",
|
||||
"Error": {
|
||||
"BothAlreadyExist": "The pack \"${pack}\" already exists and the new pack id \"${id}\" is already in use",
|
||||
"IDAlreadyExist": "The new pack id \"${id}\" is already in use",
|
||||
"NameAlreadyExist": "The pack \"${pack}\" already exists"
|
||||
}
|
||||
},
|
||||
"Discard": {
|
||||
"Title": "Discard changes?",
|
||||
"Description": "Do you want to discard your changes? This action cannot be undone"
|
||||
},
|
||||
"SaveChanges": "Save Changes",
|
||||
"DiscardChanges": "Discard Changes"
|
||||
},
|
||||
"GeneralSettings": {
|
||||
"Title": "Module General Settings",
|
||||
"Hint": "Fine tune the features to your desire.",
|
||||
"Label": "Settings"
|
||||
},
|
||||
"ActorInventorSettings": {
|
||||
"Title": "Actor Inventor Settings",
|
||||
"Hint": "Setup actor inventor settings",
|
||||
"Label": "Actor Inventor"
|
||||
},
|
||||
"DefaultAttackHook": {
|
||||
"Title": "Default Attack Hook",
|
||||
"Hint": "Hook used to trigger attack effects",
|
||||
"Options": {
|
||||
"DND5EfterAttackRoll": {
|
||||
"Label": "After JUST Attack Roll (DnD5e)"
|
||||
},
|
||||
"DND5EfterDamageRoll": {
|
||||
"Label": "After JUST Damage Roll (DnD5e)"
|
||||
},
|
||||
"MIDIAfterAttackRoll": {
|
||||
"Label": "After JUST Attack Roll (MidiQOL)",
|
||||
"DisabledMessage": "- Requires MidiQOL"
|
||||
},
|
||||
"MIDIAfterDamageRoll": {
|
||||
"Label": "After JUST Damage Roll (MidiQOL)",
|
||||
"DisabledMessage": "- Requires MidiQOL"
|
||||
},
|
||||
"MIDIAfterCompleteRoll": {
|
||||
"Label": "After Complete Roll (MidiQOL)",
|
||||
"DisabledMessage": "- Requires MidiQOL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultItemHook": {
|
||||
"Title": "Default Item Hook",
|
||||
"Hint": "Hook used to trigger item effects",
|
||||
"Options": {
|
||||
"DND5EAfterItemUse": {
|
||||
"Label": "After Item Use (DnD5e)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MasterTagConfiguration": {
|
||||
"Title": "Master Tag Configuration:",
|
||||
"Tag": {
|
||||
"MeleeSingleAttackAnimation": "Melee Single Attack Animation",
|
||||
"ThrowSingleAttackAnimation": "Throw Single Attack Animation",
|
||||
"ReturnSingleAttackAnimation": "Return Single Attack Animation",
|
||||
"RangedSingleAttackAnimation": "Ranged Single Attack Animation"
|
||||
},
|
||||
"Options": {
|
||||
"Enabled": {
|
||||
"Title": "Enabled"
|
||||
},
|
||||
"Type": {
|
||||
"Title": "Tag Type",
|
||||
"Choices": {
|
||||
"None": "None",
|
||||
"MeleeAttack": "Melee Attack",
|
||||
"RangedAttack": "Ranged Attack",
|
||||
"Lighting": "Lighting"
|
||||
}
|
||||
},
|
||||
"Weapon": {
|
||||
"MeleeAnimation": {
|
||||
"Source": {
|
||||
"Title": "Melee Animation Source"
|
||||
},
|
||||
"Delay": {
|
||||
"Title": "Melee Animation Delay"
|
||||
}
|
||||
},
|
||||
"ThrowAnimation": {
|
||||
"Source": {
|
||||
"Title": "Throw Animation Source"
|
||||
},
|
||||
"Delay": {
|
||||
"Title": "Throw Animation Delay"
|
||||
}
|
||||
},
|
||||
"ReturnAnimation": {
|
||||
"Source": {
|
||||
"Title": "Return Animation Source"
|
||||
},
|
||||
"Delay": {
|
||||
"Title": "Return Animation Delay"
|
||||
}
|
||||
},
|
||||
"RangedAnimation": {
|
||||
"Source": {
|
||||
"Title": "Ranged Animation Source"
|
||||
},
|
||||
"Delay": {
|
||||
"Title": "Ranged Animation Delay"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Lighting": {
|
||||
"Animation": {
|
||||
"Type": {
|
||||
"Title": "Type",
|
||||
"Hint": "The type of the light animation",
|
||||
"Choices": {
|
||||
"None": "None",
|
||||
"Torch": "Torch",
|
||||
"Pulse": "Pulse",
|
||||
"Chroma": "Chroma",
|
||||
"PulsingWave": "Pulsing Wave",
|
||||
"SwirlingFog": "Swirling Fog",
|
||||
"Sunburst": "Sunburst",
|
||||
"LightDome": "Fire",
|
||||
"MysteriousEmanation": "Mysterious Emanation",
|
||||
"HexaDome": "Hexa Dome",
|
||||
"GhostlyLight": "Ghostly Light",
|
||||
"EnergyField": "Energy Field",
|
||||
"RoilingMass": "Roiling Mass",
|
||||
"BlackHole": "Black Hole",
|
||||
"Vortex": "Vortex",
|
||||
"BewitchingWave": "Bewitching Wave",
|
||||
"SwirlingRainbow": "Swirling Rainbow",
|
||||
"RadialRainbow": "Radial Rainbow",
|
||||
"FairyLight": "Fairy Light",
|
||||
"ForceGrid": "Force Grid",
|
||||
"StarLight": "Start Light",
|
||||
"SmokePatch": "Smoke Patch"
|
||||
}
|
||||
},
|
||||
"Speed": {
|
||||
"Title": "Animation Speed",
|
||||
"Hint": "The speed of the light animation"
|
||||
},
|
||||
"Intensity": {
|
||||
"Title": "Animation Intensity",
|
||||
"Hint": "The intensity of the light animation"
|
||||
},
|
||||
"Reverse": {
|
||||
"Title": "Reverse Animation",
|
||||
"Hint": "Whether or not the light animation should be reversed"
|
||||
}
|
||||
},
|
||||
"Color": {
|
||||
"Title": "Lighting Color",
|
||||
"Hint": "The color of the light"
|
||||
},
|
||||
"Intensity": {
|
||||
"Title": "Lighting Intensity",
|
||||
"Hint": "The intensity of the light"
|
||||
},
|
||||
"Angle": {
|
||||
"Title": "Lighting Angle",
|
||||
"Hint": "The angle of the light"
|
||||
},
|
||||
"Icons": {
|
||||
"Unlit": {
|
||||
"Title": "Unlit Icon",
|
||||
"Hint": "The icon to be used when the light is unlit"
|
||||
},
|
||||
"Lit": {
|
||||
"Title": "Lit Icon",
|
||||
"Hint": "The icon to be used when the light is lit"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"UseAnimations": {
|
||||
"Title": "Use Animations",
|
||||
"Hint": "When disabled, OIF will only handle interactions and automations"
|
||||
},
|
||||
"MeleeAnimationDelay": {
|
||||
"Title": "Fake Delay Before Melee Animation",
|
||||
"Hint": "The delay before melee automations and interactions)"
|
||||
},
|
||||
"RangedAnimationDelay": {
|
||||
"Title": "Fake Delay Before Ranged Animation",
|
||||
"Hint": "The delay before ranged automations and interactions"
|
||||
},
|
||||
"CpLocation": {
|
||||
"Title": "Copper Pieces Location",
|
||||
"Hint": "Where are copper pieces data located inside actors"
|
||||
},
|
||||
"SpLocation": {
|
||||
"Title": "Silver Pieces Location",
|
||||
"Hint": "Where are silver pieces data located inside actors"
|
||||
},
|
||||
"GpLocation": {
|
||||
"Title": "Gold Pieces Location",
|
||||
"Hint": "Where are gold pieces data located inside actors"
|
||||
},
|
||||
"PpLocation": {
|
||||
"Title": "Platinum Pieces Location",
|
||||
"Hint": "Where are platinum pieces data located inside actors"
|
||||
},
|
||||
"EpLocation": {
|
||||
"Title": "Electrum Pieces Location",
|
||||
"Hint": "Where are electrum pieces data located inside actors"
|
||||
},
|
||||
"DefaultThrowableDestructionChance": {
|
||||
"Title": "Default Throwable Destruction Chance",
|
||||
"Hint": "Percentage that defines the default chance of throwables being destructed on impact"
|
||||
},
|
||||
"DefaultAmmunitionDestructionChance": {
|
||||
"Title": "Default Ammunition Destruction Chance",
|
||||
"Hint": "Percentage that defines the default chance of ammuntion being destructed on impact"
|
||||
},
|
||||
"RemoveThrowableItem": {
|
||||
"Title": "Remove Throwable Item",
|
||||
"Hint": "Whether or not throwable items get removed from the inventory when thrown"
|
||||
},
|
||||
"AddThrowableToTargetInventory": {
|
||||
"Title": "Add Throwable Item to Target Inventory",
|
||||
"Hint": "Whether or not throwable items get added to the target inventory when hit"
|
||||
},
|
||||
"AddAmmunitionToTargetInventory": {
|
||||
"Title": "Add Ammunition Item to Target Inventory",
|
||||
"Hint": "Whether or not ammunition items get adedd to the target inventory when hit"
|
||||
},
|
||||
"CreateItemPilesOnMiss": {
|
||||
"Title": "Create Item Piles on Miss",
|
||||
"Hint": "Whether or not Item Piles will be created when a projectile misses the target"
|
||||
},
|
||||
"CreateItemPilesOnHit": {
|
||||
"Title": "Create Item Piles on Hit",
|
||||
"Hint": "Whether or not Item Piles will be created when a projectile hits the target"
|
||||
},
|
||||
"SnapCreatedItemPilesToGrid": {
|
||||
"Title": "Snap Created Item Piles to Grid",
|
||||
"Hint": "Whether or not Item Piles created by this module should snap to grid"
|
||||
},
|
||||
"MinifyItemPilesNames": {
|
||||
"Title": "Minify Item Piles Names",
|
||||
"Hint": "Whether or not Item Piles generated by the module will have a minified name"
|
||||
},
|
||||
"SetElevationOfItemPiles": {
|
||||
"Title": "Set Elevation of Item Piles",
|
||||
"Hint": "Whether or not Item Piles created by this module should have elevation set based on target's elevation"
|
||||
},
|
||||
"PowerfulImpactShakeEffect": {
|
||||
"Title": "Powerful Impact Shake Effect",
|
||||
"Hint": "Whether or not powerful impacts will have a screen shake effect"
|
||||
},
|
||||
"LightingItemsAutomation": {
|
||||
"Title": "Lighting Items Automation",
|
||||
"Hint": "Whether or not lighting items should be automated with tags"
|
||||
},
|
||||
"DeveloperMode": {
|
||||
"Title": "Developer Mode",
|
||||
"Hint": "Logs useful information to the console !! THIS IS FOR DEVELOPERS ONLY !!"
|
||||
},
|
||||
"CurrencyGenerator": {
|
||||
"Title": "Currency Generator",
|
||||
"Hint": "Whether or not actors will have their currency generated"
|
||||
},
|
||||
"ModuleRequired": "Module ${module} is required",
|
||||
"OptionRequired": "Requires ${option} to be enabled",
|
||||
"OptionUnrequired": "Requires ${option} to be disabled",
|
||||
"RestartRequired": "Requires Foundry to be restarted!!"
|
||||
}
|
||||
}
|
||||
}
|
||||
197
src/modules/objects-interactions-fx/module/scripts/Main.js
Normal file
197
src/modules/objects-interactions-fx/module/scripts/Main.js
Normal file
@@ -0,0 +1,197 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "./ObjectsInteractionsFX.js";
|
||||
import { Settings } from "./Settings.js";
|
||||
import { SystemSupporter } from "./system/SystemSupporter.js";
|
||||
import { MasterTagsSettings } from "./interface/MasterTagsSettings.js";
|
||||
import { GeneralSettings } from "./interface/GeneralSettings.js";
|
||||
import { ObjectsInteractionsFXData } from "./data/ObjectsInteractionsFXData.js";
|
||||
import { TagHandler } from "./tags/TagHandler.js";
|
||||
import { TokenLightingManipulator } from "./library/TokenLightingManipulator.js";
|
||||
import { Debug as DBG } from "./library/Debug.js";
|
||||
|
||||
Hooks.on("init", () =>
|
||||
{
|
||||
console.log("%cObject Interaction FX", `
|
||||
color:#FF0088;
|
||||
background-color:white;
|
||||
font-size:25pt;
|
||||
font-weight:bold;
|
||||
padding:15pt;
|
||||
`);
|
||||
|
||||
OIF.Initialize();
|
||||
|
||||
Hooks.on('getSceneControlButtons', (controls) => {
|
||||
if (!canvas.scene) return;
|
||||
|
||||
const MasterTags = {
|
||||
name: 'master-tags',
|
||||
title: game.i18n.localize('OIF.Tooltips.MasterTags.Title'),
|
||||
icon: 'fas fa-tags',
|
||||
onClick: async () => {
|
||||
new MasterTagsSettings().render(true);
|
||||
},
|
||||
button: true
|
||||
}
|
||||
|
||||
const ClearLighting = {
|
||||
name: 'clear-lighting',
|
||||
title: game.i18n.localize('OIF.Tooltips.ClearLighting.Title'),
|
||||
icon: 'fas fa-lightbulb-slash',
|
||||
onClick: async () => {
|
||||
TokenLightingManipulator.RemoveAllLighting();
|
||||
},
|
||||
button: true
|
||||
}
|
||||
|
||||
const Configuration =
|
||||
{
|
||||
name: 'configuration',
|
||||
title: game.i18n.localize('OIF.Tooltips.Configuration.Title'),
|
||||
icon: 'fas fa-gears',
|
||||
onClick: async () => {
|
||||
new GeneralSettings().render(true);
|
||||
},
|
||||
button: true
|
||||
}
|
||||
|
||||
controls.push({
|
||||
name: OIF.ID,
|
||||
title: OIF.NAME,
|
||||
layer: 'CanvasEffects',
|
||||
icon: 'fas fa-snowflake',
|
||||
visible: game.user.isGM,
|
||||
tools: [
|
||||
MasterTags,
|
||||
ClearLighting,
|
||||
Configuration
|
||||
]
|
||||
});
|
||||
})
|
||||
|
||||
Hooks.on("ready", async () => {
|
||||
await SystemSupporter.Initialize();
|
||||
await Settings.Initialize();
|
||||
await DBG.Initialize();
|
||||
|
||||
// Check for missing modules
|
||||
let requiredModules = game.modules.get(OIF.ID).relationships.requires;
|
||||
for (let module of requiredModules) {
|
||||
if (!(game.modules.get(module.id)?.active)) {
|
||||
ui.notifications.error(game.i18n.localize('OIF.Core.MissingRequiredModule').replace('$module', module.id));
|
||||
}
|
||||
}
|
||||
|
||||
// Create the folders that are going to be used
|
||||
if (game.user.isGM)
|
||||
{
|
||||
// Create the root folder if it doesn't exist
|
||||
let Folders = await FilePicker.browse(OIF.FILES.ORIGIN, '.');
|
||||
if (!Folders.dirs.includes(OIF.FILES.DATA_FOLDERS.ROOT))
|
||||
{
|
||||
console.warn("Root folder doesn't exist, creating it...");
|
||||
await FilePicker.createDirectory(OIF.FILES.ORIGIN, OIF.FILES.DATA_FOLDERS.ROOT);
|
||||
}
|
||||
|
||||
// Create the default tag packs file if it doesn't exist
|
||||
Folders = await FilePicker.browse(OIF.FILES.ORIGIN, OIF.FILES.DATA_FOLDERS.ROOT);
|
||||
if (!Folders.files.includes(`${OIF.FILES.DATA_FOLDERS.ROOT}/TagPacks.json`))
|
||||
{
|
||||
console.warn("TagPacks.json doesn't exist, creating it...");
|
||||
let Data = {};
|
||||
await ObjectsInteractionsFXData.SaveJSON(Data, 'TagPacks.json', OIF.FILES.DATA_FOLDERS.ROOT);
|
||||
}
|
||||
}
|
||||
|
||||
DBG.Log('First breakpoint');
|
||||
|
||||
// Load default packs
|
||||
await MasterTagsSettings.LoadFromConfig();
|
||||
|
||||
DBG.Log('Second breakpoint');
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Hooks to attach
|
||||
////////////////////////////////////////////////////////////
|
||||
let HooksToAttach =
|
||||
{
|
||||
attack:
|
||||
{
|
||||
hook: GeneralSettings.Get(OIF.SETTINGS.GENERAL.ATTACH_HOOKS.ATTACK),
|
||||
id : 0
|
||||
},
|
||||
item:
|
||||
{
|
||||
hook: GeneralSettings.Get(OIF.SETTINGS.GENERAL.ATTACH_HOOKS.ITEM),
|
||||
id : 0
|
||||
}
|
||||
}
|
||||
Hooks.on(OIF.HOOKS.CHANGE_SETTINGS, async (settings) =>
|
||||
{
|
||||
DBG.Log('Changing settings', settings);
|
||||
|
||||
// Update the hooks to attach
|
||||
HooksToAttach.attack.hook = GeneralSettings.Get(OIF.SETTINGS.GENERAL.ATTACH_HOOKS.ATTACK);
|
||||
HooksToAttach.item.hook = GeneralSettings.Get(OIF.SETTINGS.GENERAL.ATTACH_HOOKS.ITEM);
|
||||
Hooks.call(OIF.HOOKS.ATTACH_HOOKS);
|
||||
});
|
||||
Hooks.on(OIF.HOOKS.ATTACH_HOOKS, async () =>
|
||||
{
|
||||
DBG.Log('Attaching hooks', HooksToAttach);
|
||||
|
||||
if (HooksToAttach.attack.id != 0)
|
||||
{
|
||||
Hooks.off(HooksToAttach.attack.hook, HooksToAttach.attack.id);
|
||||
}
|
||||
if (HooksToAttach.item.id != 0)
|
||||
{
|
||||
Hooks.off(HooksToAttach.item.hook, HooksToAttach.item.id);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Attack Hook
|
||||
////////////////////////////////////////////////////////////
|
||||
HooksToAttach.attack.id = Hooks.on(HooksToAttach.attack.hook, async (arg1, arg2, arg3) =>
|
||||
{
|
||||
// Extract relevant information
|
||||
let Workflow = [arg1, arg2, arg3];
|
||||
let Options = await SystemSupporter.ExtractOptions(Workflow, 'attack', HooksToAttach.attack.hook);
|
||||
|
||||
// Start the workflow
|
||||
Hooks.call(OIF.HOOKS.WORKFLOW.POST_PREPARE, Options);
|
||||
DBG.Log('Post prepare hook called from attack hook', HooksToAttach.attack.hook, Options);
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Item Hook
|
||||
////////////////////////////////////////////////////////////
|
||||
HooksToAttach.item.id = Hooks.on(HooksToAttach.item.hook, async (arg1, arg2, arg3) =>
|
||||
{
|
||||
// Extract relevant information
|
||||
let Workflow = [arg1, arg2, arg3];
|
||||
let Options = await SystemSupporter.ExtractOptions(Workflow, 'item', HooksToAttach.item.hook);
|
||||
|
||||
// Start the workflow
|
||||
Hooks.call(OIF.HOOKS.WORKFLOW.POST_PREPARE, Options);
|
||||
DBG.Log('Post prepare hook called from item hook', HooksToAttach.item.hook, Options);
|
||||
});
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Main workflow
|
||||
////////////////////////////////////////////////////////////
|
||||
Hooks.on(OIF.HOOKS.WORKFLOW.POST_PREPARE, async (options) =>
|
||||
{
|
||||
DBG.Log('Post prepare hook called', options);
|
||||
// Check if there are tags to be used
|
||||
if (options.tags.length > 0)
|
||||
{
|
||||
// Send tags to the handler
|
||||
TagHandler.Dispatch(options);
|
||||
}
|
||||
});
|
||||
|
||||
Hooks.call(OIF.HOOKS.ATTACH_HOOKS);
|
||||
Hooks.callAll("oifReady", game.modules.get(OIF.ID).api);
|
||||
console.log("Automated Objects, Interactions and Effects is ready!!");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,174 @@
|
||||
export class ObjectsInteractionsFX
|
||||
{
|
||||
static ID = 'objects-interactions-fx';
|
||||
static NAME = 'Automated Objects, Interactions and Effects';
|
||||
|
||||
static FLAGS = {
|
||||
OIF: 'OIF',
|
||||
}
|
||||
|
||||
static TEMPLATES = {
|
||||
MASTER_TAGS_SETTINGS : `modules/${this.ID}/module/templates/MasterTagsSettings.hbs`,
|
||||
MASTER_TAG_CONFIGURATION: `modules/${this.ID}/module/templates/MasterTagConfiguration.hbs`,
|
||||
SETTINGS_SKELETON : `modules/${this.ID}/module/templates/SettingsSkeleton.hbs`,
|
||||
CONFIG_SKELETON : `modules/${this.ID}/module/templates/ConfigSkeleton.hbs`,
|
||||
}
|
||||
|
||||
static SOCKET;
|
||||
|
||||
static SETTINGS = {
|
||||
GENERAL: {
|
||||
ATTACH_HOOKS: {
|
||||
ATTACK: 'hookAttachAttack',
|
||||
ITEM : 'hookAttachItem',
|
||||
},
|
||||
MASTER_TAGS_SETTINGS : 'masterTagsSettings',
|
||||
GENERAL_SETTINGS : 'generalSettings',
|
||||
ACTOR_INVENTOR_SETTINGS : 'actorInventor',
|
||||
DEFAULT_ATTACK_HOOK : 'defaultAttackHook',
|
||||
DEFAULT_ITEM_HOOK : 'defaultItemHook',
|
||||
USE_ANIMATIONS : 'useAnimations',
|
||||
MELEE_ANIMATION_DELAY : 'animationDelay',
|
||||
RANGED_ANIMATION_DELAY : 'rangedAnimationDelay',
|
||||
DEFAULT_AMMUNITION_DESTRUCTION_CHANCE: 'defaultAmmunitionDestructionChance',
|
||||
DEFAULT_THROWABLE_DESTRUCTION_CHANCE : 'defaultThrowableDestructionChance',
|
||||
REMOVE_THROWABLE_ITEM : 'removeThrowableItem',
|
||||
ADD_THROWABLE_TO_TARGET_INVENTORY : 'addThrowableToTargetInventory',
|
||||
ADD_AMMUNITION_TO_TARGET_INVENTORY : 'addAmmunitionToTargetInventory',
|
||||
CREATE_ITEM_PILES_ON_MISS : 'createItemPilesOnMiss',
|
||||
CREATE_ITEM_PILES_ON_HIT : 'createItemPilesOnHit',
|
||||
SNAP_CREATED_ITEM_PILES_TO_GRID : 'snapCreatedItemPilesToGrid',
|
||||
MINIFY_ITEM_PILES_NAMES : 'minifyItemPilesNames',
|
||||
POWERFUL_IMPACT_SHAKE_EFFECT : 'powerfulImpactShakeEffect',
|
||||
SET_ELEVATION_OF_ITEM_PILES : 'setElevationOfItemPiles',
|
||||
LIGHTING_ITEMS_AUTOMATION : 'lightingItemsAutomation',
|
||||
DEVELOPER_MODE : 'developerMode',
|
||||
},
|
||||
|
||||
ACTOR_INVENTOR: {
|
||||
CURRENCY_GENERATOR: 'currencyGenerator',
|
||||
CP_LOCATION : 'copperPiecesLocation',
|
||||
SP_LOCATION : 'silverPiecesLocation',
|
||||
GP_LOCATION : 'goldPiecesLocation',
|
||||
PP_LOCATION : 'platinumPiecesLocation',
|
||||
EP_LOCATION : 'electrumPiecesLocation',
|
||||
},
|
||||
|
||||
MASTER_TAGS: {
|
||||
CURRENT_TAG_PACK: 'currentTagPack',
|
||||
}
|
||||
}
|
||||
|
||||
static FILES = {
|
||||
ORIGIN : 'data',
|
||||
DATA_FOLDERS: {
|
||||
DEFAULT_TAG_PACKS: `modules/${this.ID}/module/data/defaultTagPacks`,
|
||||
ROOT : 'oif'
|
||||
}
|
||||
}
|
||||
|
||||
static HOOKS = {
|
||||
CHANGE_SETTINGS: 'oif.ChangeSettings',
|
||||
ATTACH_HOOKS : 'oif.attachHooks',
|
||||
WORKFLOW: {
|
||||
POST_PREPARE: 'oifWorkflowPostPrepare',
|
||||
POST_EXECUTE: 'oifWorkflowPostExecute',
|
||||
},
|
||||
ITEM: {
|
||||
LIGHTING: {
|
||||
POST_PREPARE: 'oifItemLightingPostPrepare',
|
||||
LIGHT: {
|
||||
POST_APPLY: 'oifItemLightingLightPostApply',
|
||||
POST_SOUND: 'oifPostItemLightingLightSound',
|
||||
},
|
||||
EXTINGUISH: {
|
||||
POST_APPLY: 'oifItemLightingExtinguishPostApply',
|
||||
POST_SOUND: 'oifPostItemLightingExtinguishSound',
|
||||
},
|
||||
},
|
||||
},
|
||||
WEAPON: {
|
||||
MELEE: {
|
||||
POST_PREPARE: 'oifWeaponMeleePostPrepare',
|
||||
HIT: {
|
||||
POST_ANIMATION : 'oifWeaponMeleeHitPostAnimation',
|
||||
POST_INTERACTION: 'oifWeaponMeleeHitPostInteraction',
|
||||
POST_SOUND : 'oifWeaponMeleeHitPostSound',
|
||||
},
|
||||
THROW: {
|
||||
POST_ANIMATION : 'oifWeaponMeleeThrowPostAnimation',
|
||||
POST_INTERACTION: 'oifWeaponMeleeThrowPostInteraction',
|
||||
POST_SOUND : 'oifWeaponMeleeThrowPostSound',
|
||||
},
|
||||
},
|
||||
RANGED: {
|
||||
POST_PREPARE: 'oifWeaponRangedPostPrepare',
|
||||
HIT: {
|
||||
POST_ANIMATION : 'oifWeaponRangedHitPostAnimation',
|
||||
POST_INTERACTION: 'oifWeaponRangedHitPostInteraction',
|
||||
POST_SOUND : 'oifWeaponRangedHitPostSound',
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static OPTIONAL_MODULES = {
|
||||
MIDI_QOL: {
|
||||
id : 'midi-qol',
|
||||
name : 'MidiQOL',
|
||||
active: false,
|
||||
},
|
||||
LEVELS: {
|
||||
id : 'levels',
|
||||
name : 'Levels',
|
||||
active: false,
|
||||
},
|
||||
ITEM_PILES: {
|
||||
id : 'item-piles',
|
||||
name : 'Item Piles',
|
||||
active: false,
|
||||
},
|
||||
TAGGER: {
|
||||
id : 'tagger',
|
||||
name : 'Tagger',
|
||||
active: false,
|
||||
},
|
||||
TIDY_SHEET: {
|
||||
id : 'tidy5e-sheet',
|
||||
name : 'Tidy5e Sheet',
|
||||
active: false,
|
||||
}
|
||||
}
|
||||
|
||||
static WORKFLOW = {
|
||||
DATA: {
|
||||
TYPE: {
|
||||
ACTION: {
|
||||
ATTACK: {
|
||||
MELEE : 'meleeAttack',
|
||||
RANGED: 'rangedAttack',
|
||||
},
|
||||
USE: {
|
||||
ITEM: 'useItem',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Initialize()
|
||||
{
|
||||
// Check if optional modules are loaded
|
||||
for (const key in this.OPTIONAL_MODULES)
|
||||
{
|
||||
if (game.modules.get(this.OPTIONAL_MODULES[key].id)?.active)
|
||||
{
|
||||
this.OPTIONAL_MODULES[key].active = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
console.warn(`OIF | module ${this.OPTIONAL_MODULES[key].name} not active - some features disabled`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
276
src/modules/objects-interactions-fx/module/scripts/Settings.js
Normal file
276
src/modules/objects-interactions-fx/module/scripts/Settings.js
Normal file
@@ -0,0 +1,276 @@
|
||||
import { GeneralSettings } from "./interface/GeneralSettings.js";
|
||||
import { MasterTagsSettings } from "./interface/MasterTagsSettings.js";
|
||||
import { ActorInventorSettings } from "./interface/ActorInventorSettings.js";
|
||||
import { ObjectsInteractionsFX as OIF } from "./ObjectsInteractionsFX.js";
|
||||
import { SettingsSkeleton } from "./library/skeletons/SettingsSkeleton.js";
|
||||
import { SystemSupporter } from "./system/SystemSupporter.js";
|
||||
|
||||
export class Settings
|
||||
{
|
||||
static async Initialize()
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// Register Master Tags menu
|
||||
game.settings.registerMenu(OIF.ID, OIF.SETTINGS.GENERAL.MASTER_TAGS_SETTINGS, {
|
||||
name: 'OIF.Settings.MasterTagsSettings.Title',
|
||||
hint: 'OIF.Settings.MasterTagsSettings.Hint',
|
||||
label: 'OIF.Settings.MasterTagsSettings.Label',
|
||||
icon: 'fas fa-tags',
|
||||
type: MasterTagsSettings,
|
||||
restricted: true,
|
||||
});
|
||||
await MasterTagsSettings.Register();
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Register General Settings menu
|
||||
game.settings.registerMenu(OIF.ID, OIF.SETTINGS.GENERAL.GENERAL_SETTINGS, {
|
||||
name: 'OIF.Settings.GeneralSettings.Title',
|
||||
hint: 'OIF.Settings.GeneralSettings.Hint',
|
||||
label: 'OIF.Settings.GeneralSettings.Label',
|
||||
icon: 'fas fa-cog',
|
||||
type: GeneralSettings,
|
||||
restricted: false,
|
||||
});
|
||||
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.ATTACH_HOOKS.ATTACK, {
|
||||
name: 'OIF.Settings.DefaultAttackHook.Title',
|
||||
hint: 'OIF.Settings.DefaultAttackHook.Hint',
|
||||
scope: 'world',
|
||||
type: 'dropdown',
|
||||
default: SystemSupporter.GetDefaultHookAttack(),
|
||||
choices: SystemSupporter.GetPossibleHooksAttack()
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.ATTACH_HOOKS.ITEM, {
|
||||
name: 'OIF.Settings.DefaultItemHook.Title',
|
||||
hint: 'OIF.Settings.DefaultItemHook.Hint',
|
||||
scope: 'world',
|
||||
type: 'dropdown',
|
||||
default: SystemSupporter.GetDefaultHookItem(),
|
||||
choices: SystemSupporter.GetPossibleHooksItem()
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.USE_ANIMATIONS, {
|
||||
name: 'OIF.Settings.UseAnimations.Title',
|
||||
hint: 'OIF.Settings.UseAnimations.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
default: true,
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.MELEE_ANIMATION_DELAY, {
|
||||
name: 'OIF.Settings.MeleeAnimationDelay.Title',
|
||||
hint: 'OIF.Settings.MeleeAnimationDelay.Hint',
|
||||
scope: 'world',
|
||||
type: 'slider',
|
||||
range: {
|
||||
min: 0,
|
||||
max: 3000,
|
||||
step: 1,
|
||||
},
|
||||
default: 1950,
|
||||
excludesOn: [OIF.SETTINGS.GENERAL.USE_ANIMATIONS],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.RANGED_ANIMATION_DELAY, {
|
||||
name: 'OIF.Settings.RangedAnimationDelay.Title',
|
||||
hint: 'OIF.Settings.RangedAnimationDelay.Hint',
|
||||
scope: 'world',
|
||||
type: 'slider',
|
||||
range: {
|
||||
min: 0,
|
||||
max: 3000,
|
||||
step: 1,
|
||||
},
|
||||
default: 850,
|
||||
excludesOn: [OIF.SETTINGS.GENERAL.USE_ANIMATIONS],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.REMOVE_THROWABLE_ITEM, {
|
||||
name: 'OIF.Settings.RemoveThrowableItem.Title',
|
||||
hint: 'OIF.Settings.RemoveThrowableItem.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
default: false,
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.ADD_THROWABLE_TO_TARGET_INVENTORY, {
|
||||
name: 'OIF.Settings.AddThrowableToTargetInventory.Title',
|
||||
hint: 'OIF.Settings.AddThrowableToTargetInventory.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
default: false,
|
||||
dependsOn: [OIF.SETTINGS.GENERAL.REMOVE_THROWABLE_ITEM],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.ADD_AMMUNITION_TO_TARGET_INVENTORY, {
|
||||
name: 'OIF.Settings.AddAmmunitionToTargetInventory.Title',
|
||||
hint: 'OIF.Settings.AddAmmunitionToTargetInventory.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
default: false,
|
||||
dependsOn: [OIF.SETTINGS.GENERAL.REMOVE_THROWABLE_ITEM],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_MISS, {
|
||||
name: 'OIF.Settings.CreateItemPilesOnMiss.Title',
|
||||
hint: 'OIF.Settings.CreateItemPilesOnMiss.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.ITEM_PILES,
|
||||
default: OIF.OPTIONAL_MODULES.ITEM_PILES.active,
|
||||
dependsOn: [OIF.SETTINGS.GENERAL.REMOVE_THROWABLE_ITEM],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_HIT, {
|
||||
name: 'OIF.Settings.CreateItemPilesOnHit.Title',
|
||||
hint: 'OIF.Settings.CreateItemPilesOnHit.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.ITEM_PILES,
|
||||
default: false,
|
||||
dependsOn: [OIF.SETTINGS.GENERAL.REMOVE_THROWABLE_ITEM],
|
||||
excludesOn: [OIF.SETTINGS.GENERAL.ADD_THROWABLE_TO_TARGET_INVENTORY, OIF.SETTINGS.GENERAL.ADD_AMMUNITION_TO_TARGET_INVENTORY],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.SNAP_CREATED_ITEM_PILES_TO_GRID, {
|
||||
name: 'OIF.Settings.SnapCreatedItemPilesToGrid.Title',
|
||||
hint: 'OIF.Settings.SnapCreatedItemPilesToGrid.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.ITEM_PILES,
|
||||
default: OIF.OPTIONAL_MODULES.ITEM_PILES.active,
|
||||
dependsOn: [OIF.SETTINGS.GENERAL.REMOVE_THROWABLE_ITEM],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.MINIFY_ITEM_PILES_NAMES, {
|
||||
name: 'OIF.Settings.MinifyItemPilesNames.Title',
|
||||
hint: 'OIF.Settings.MinifyItemPilesNames.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.ITEM_PILES,
|
||||
default: OIF.OPTIONAL_MODULES.ITEM_PILES.active,
|
||||
dependsOn: [OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_MISS],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.DEFAULT_THROWABLE_DESTRUCTION_CHANCE, {
|
||||
name: 'OIF.Settings.DefaultThrowableDestructionChance.Title',
|
||||
hint: 'OIF.Settings.DefaultThrowableDestructionChance.Hint',
|
||||
scope: 'world',
|
||||
type: 'slider',
|
||||
range: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
default: 0,
|
||||
dependsOn: [OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_MISS],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.DEFAULT_AMMUNITION_DESTRUCTION_CHANCE, {
|
||||
name: 'OIF.Settings.DefaultAmmunitionDestructionChance.Title',
|
||||
hint: 'OIF.Settings.DefaultAmmunitionDestructionChance.Hint',
|
||||
scope: 'world',
|
||||
type: 'slider',
|
||||
range: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
default: 50,
|
||||
dependsOn: [OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_MISS],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.SET_ELEVATION_OF_ITEM_PILES, {
|
||||
name: 'OIF.Settings.SetElevationOfItemPiles.Title',
|
||||
hint: 'OIF.Settings.SetElevationOfItemPiles.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
disabled: !(OIF.OPTIONAL_MODULES.LEVELS.active && OIF.OPTIONAL_MODULES.ITEM_PILES.active),
|
||||
default: OIF.OPTIONAL_MODULES.LEVELS.active && OIF.OPTIONAL_MODULES.ITEM_PILES.active,
|
||||
dependsOn: [OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_MISS],
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.POWERFUL_IMPACT_SHAKE_EFFECT, {
|
||||
name: 'OIF.Settings.PowerfulImpactShakeEffect.Title',
|
||||
hint: 'OIF.Settings.PowerfulImpactShakeEffect.Hint',
|
||||
scope: 'client',
|
||||
type: 'checkbox',
|
||||
default: true,
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.LIGHTING_ITEMS_AUTOMATION, {
|
||||
name: 'OIF.Settings.LightingItemsAutomation.Title',
|
||||
hint: 'OIF.Settings.LightingItemsAutomation.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.TAGGER,
|
||||
disabled: !OIF.OPTIONAL_MODULES.TAGGER.active,
|
||||
default: OIF.OPTIONAL_MODULES.TAGGER.active,
|
||||
});
|
||||
GeneralSettings.Register(OIF.SETTINGS.GENERAL.DEVELOPER_MODE, {
|
||||
name: 'OIF.Settings.DeveloperMode.Title',
|
||||
hint: 'OIF.Settings.DeveloperMode.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
default: false,
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Register Actor Inventor Settings menu
|
||||
game.settings.registerMenu(OIF.ID, OIF.SETTINGS.GENERAL.ACTOR_INVENTOR_SETTINGS, {
|
||||
name: 'OIF.Settings.ActorInventorSettings.Title',
|
||||
hint: 'OIF.Settings.ActorInventorSettings.Hint',
|
||||
label: 'OIF.Settings.ActorInventorSettings.Label',
|
||||
icon: 'fas fa-user',
|
||||
type: ActorInventorSettings,
|
||||
restricted: false,
|
||||
});
|
||||
ActorInventorSettings.Register(OIF.SETTINGS.ACTOR_INVENTOR.CURRENCY_GENERATOR, {
|
||||
name: 'OIF.Settings.CurrencyGenerator.Title',
|
||||
hint: 'OIF.Settings.CurrencyGenerator.Hint',
|
||||
scope: 'world',
|
||||
type: 'checkbox',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
disabled: !OIF.OPTIONAL_MODULES.TIDY_SHEET.active,
|
||||
default: OIF.OPTIONAL_MODULES.TIDY_SHEET.active,
|
||||
});
|
||||
ActorInventorSettings.Register(OIF.SETTINGS.ACTOR_INVENTOR.CP_LOCATION, {
|
||||
name: 'OIF.Settings.CpLocation.Title',
|
||||
hint: 'OIF.Settings.CpLocation.Hint',
|
||||
scope: 'world',
|
||||
type: 'string',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
disabled: !OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
default: "system.currency.cp",
|
||||
dependsOn: [OIF.SETTINGS.ACTOR_INVENTOR.CURRENCY_GENERATOR],
|
||||
});
|
||||
ActorInventorSettings.Register(OIF.SETTINGS.ACTOR_INVENTOR.SP_LOCATION, {
|
||||
name: 'OIF.Settings.SpLocation.Title',
|
||||
hint: 'OIF.Settings.SpLocation.Hint',
|
||||
scope: 'world',
|
||||
type: 'string',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
disabled: !OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
default: "system.currency.sp",
|
||||
dependsOn: [OIF.SETTINGS.ACTOR_INVENTOR.CURRENCY_GENERATOR],
|
||||
});
|
||||
ActorInventorSettings.Register(OIF.SETTINGS.ACTOR_INVENTOR.GP_LOCATION, {
|
||||
name: 'OIF.Settings.GpLocation.Title',
|
||||
hint: 'OIF.Settings.GpLocation.Hint',
|
||||
scope: 'world',
|
||||
type: 'string',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
disabled: !OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
default: "system.currency.gp",
|
||||
dependsOn: [OIF.SETTINGS.ACTOR_INVENTOR.CURRENCY_GENERATOR],
|
||||
});
|
||||
ActorInventorSettings.Register(OIF.SETTINGS.ACTOR_INVENTOR.PP_LOCATION, {
|
||||
name: 'OIF.Settings.PpLocation.Title',
|
||||
hint: 'OIF.Settings.PpLocation.Hint',
|
||||
scope: 'world',
|
||||
type: 'string',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
disabled: !OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
default: "system.currency.pp",
|
||||
dependsOn: [OIF.SETTINGS.ACTOR_INVENTOR.CURRENCY_GENERATOR],
|
||||
});
|
||||
ActorInventorSettings.Register(OIF.SETTINGS.ACTOR_INVENTOR.EP_LOCATION, {
|
||||
name: 'OIF.Settings.EpLocation.Title',
|
||||
hint: 'OIF.Settings.EpLocation.Hint',
|
||||
scope: 'world',
|
||||
type: 'string',
|
||||
requiredModule: OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
disabled: !OIF.OPTIONAL_MODULES.TIDY_SHEET,
|
||||
default: "system.currency.ep",
|
||||
dependsOn: [OIF.SETTINGS.ACTOR_INVENTOR.CURRENCY_GENERATOR],
|
||||
});
|
||||
|
||||
GeneralSettings.UpdateSettings();
|
||||
ActorInventorSettings.UpdateSettings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
import { ItemDropper} from "../library/ItemDropper.js";
|
||||
import { InventoryManipulator } from "../library/InventoryManipulator.js";
|
||||
import { GeneralSettings } from "../interface/GeneralSettings.js";
|
||||
import { Helpers } from "../library/Helpers.js";
|
||||
import { TokenLightingManipulator } from "../library/TokenLightingManipulator.js";
|
||||
import { Debug as DBG } from "../library/Debug.js";
|
||||
|
||||
export class ItemAnimator
|
||||
{
|
||||
static async GetLandedPosAnimated(options, sequenceIdentifier)
|
||||
{
|
||||
let [effect] = Sequencer.EffectManager.getEffects({ name: sequenceIdentifier });
|
||||
options.landedPosX = effect.targetPosition.x - options.gridSize / 2;
|
||||
options.landedPosY = effect.targetPosition.y - options.gridSize / 2;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Get a random position
|
||||
//////////////////////////////////////////////////
|
||||
static GetLandedPosRandomCorner(options)
|
||||
{
|
||||
let corners = Helpers.GetCornersOfToken(options.target.document);
|
||||
let randomCorner = corners[Math.floor(Math.random() * corners.length)];
|
||||
options.landedPosX = randomCorner.x;
|
||||
options.landedPosY = randomCorner.y;
|
||||
return options;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Validate and prepare the data to be usede
|
||||
// inside other functions, everything that needs
|
||||
// to be validated and prepared should be here
|
||||
//////////////////////////////////////////////////
|
||||
static ValidateAndPrepare(options)
|
||||
{
|
||||
// Check if there is no targets
|
||||
if (options.targets == undefined || options.targets.length == 0)
|
||||
{
|
||||
// Stop the workflow if there is no target
|
||||
options.stopWorkflow = true;
|
||||
return options;
|
||||
}
|
||||
|
||||
// Get the first target (only valid)
|
||||
options.target = options.targets[0];
|
||||
|
||||
// Calculate de distance between author and target
|
||||
options.distance = canvas.grid.measureDistance(options.token, options.target);
|
||||
|
||||
// Get grid unit size
|
||||
options.gridUnitSize = canvas.dimensions.distance;
|
||||
|
||||
// Get grid size
|
||||
options.gridSize = canvas.dimensions.size;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* * MeleeWeaponSingleAttack
|
||||
* This is the function responsible for melee weapon attacks with
|
||||
* single targets
|
||||
*/
|
||||
static async MeleeWeaponSingleAttack(options)
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Preparation
|
||||
////////////////////////////////////////////////////////////
|
||||
// Validate and prepare for the workflow
|
||||
options = ItemAnimator.ValidateAndPrepare(options);
|
||||
if (options.stopWorkflow == true) { return; }
|
||||
|
||||
Hooks.call(OIF.HOOKS.WEAPON.MELEE.POST_PREPARE, options);
|
||||
|
||||
// Check if item can be thrown at the current distance
|
||||
if (options.system.isThrowable && options.distance >= options.system.meleeWeaponDistance + options.gridUnitSize)
|
||||
{
|
||||
// Check if the distance is below max distance
|
||||
if (options.distance <= options.system.longDistance)
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Thrown Attack
|
||||
////////////////////////////////////////////////////////////
|
||||
// TODO: consult system rules to see if we should do a correction to the throw attack
|
||||
|
||||
// Check if animation should be played
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.USE_ANIMATIONS) && options.throwAnimation != undefined)
|
||||
{
|
||||
// Define throw sequence to be played
|
||||
let SequenceIdentifier = `${options.name}-throw-${options.token.document._id}`;
|
||||
let SequenceEffect = new Sequence(OIF.ID)
|
||||
.effect()
|
||||
.file(options.throwAnimation.source)
|
||||
.atLocation(options.token)
|
||||
.stretchTo(options.target)
|
||||
.name(SequenceIdentifier)
|
||||
.missed(options.miss ?? false)
|
||||
|
||||
// Play throw sequence
|
||||
await SequenceEffect.play();
|
||||
|
||||
// Get where the effect landed
|
||||
options = await ItemAnimator.GetLandedPosAnimated(options, SequenceIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get a random place to simulate where the projectile landed
|
||||
options = await ItemAnimator.GetLandedPosRandomCorner(options);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Thrown Attack Interaction
|
||||
//////////////////////////////////////////////////////////////
|
||||
setTimeout(() => {
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.USE_ANIMATIONS))
|
||||
{
|
||||
Hooks.call(OIF.HOOKS.WEAPON.MELEE.THROW.POST_ANIMATION, options);
|
||||
}
|
||||
|
||||
// Create a copy of the item
|
||||
let ItemCopy = options.item.toObject();
|
||||
ItemCopy.system.quantity = 1;
|
||||
|
||||
let DidInteract = false;
|
||||
// Check if the item should or not break
|
||||
if(!(Helpers.RandomMax(100) <= GeneralSettings.Get(OIF.SETTINGS.GENERAL.DEFAULT_THROWABLE_DESTRUCTION_CHANCE)))
|
||||
{
|
||||
let CreateItemPileOnMiss = GeneralSettings.Get(OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_MISS);
|
||||
let CreateItemPileOnHit = GeneralSettings.Get(OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_HIT);
|
||||
let AddThrowableToTargetInventory = GeneralSettings.Get(OIF.SETTINGS.GENERAL.ADD_THROWABLE_TO_TARGET_INVENTORY);
|
||||
let RemoveThrowableItem = GeneralSettings.Get(OIF.SETTINGS.GENERAL.REMOVE_THROWABLE_ITEM);
|
||||
|
||||
let ShouldRemoveItem = false;
|
||||
|
||||
// Check if the attack missed and if a item pile should be created
|
||||
if (options.miss && CreateItemPileOnMiss)
|
||||
{
|
||||
// Setup item pile position
|
||||
let ItemPilePosition = {
|
||||
x: options.landedPosX,
|
||||
y: options.landedPosY,
|
||||
}
|
||||
|
||||
// Drop item
|
||||
ItemDropper.DropAt(options.item, 1, ItemPilePosition, options.target.document.elevation);
|
||||
ShouldRemoveItem = true;
|
||||
}
|
||||
else if (AddThrowableToTargetInventory)
|
||||
{
|
||||
// Add item to target's inventory
|
||||
InventoryManipulator.AddItem(options.target, options.item, 1);
|
||||
ShouldRemoveItem = true;
|
||||
}
|
||||
else if (CreateItemPileOnHit)
|
||||
{
|
||||
options = ItemAnimator.GetLandedPosRandomCorner(options);
|
||||
|
||||
// Setup item pile position
|
||||
let ItemPilePosition = {
|
||||
x: options.landedPosX,
|
||||
y: options.landedPosY,
|
||||
}
|
||||
|
||||
// Drop item
|
||||
ItemDropper.DropAt(options.item, 1, ItemPilePosition, options.target.document.elevation);
|
||||
ShouldRemoveItem = true;
|
||||
}
|
||||
|
||||
// Check if item should be removed
|
||||
if (RemoveThrowableItem && ShouldRemoveItem)
|
||||
{
|
||||
// Remove item from author inventory
|
||||
InventoryManipulator.RemoveItem(options.token, options.item, 1);
|
||||
}
|
||||
|
||||
DidInteract = true;
|
||||
}
|
||||
|
||||
// Check if poweful impact effect should be played
|
||||
if ((options.throwAnimation.powerful || options.tags.includes('powerful')) && GeneralSettings.Get(OIF.SETTINGS.GENERAL.POWERFUL_IMPACT_SHAKE_EFFECT))
|
||||
{
|
||||
OIF_SOCKET.executeForEveryone('ScreenShake');
|
||||
DidInteract = true;
|
||||
}
|
||||
|
||||
if (DidInteract)
|
||||
{
|
||||
Hooks.call(OIF.HOOKS.WEAPON.MELEE.THROW.POST_INTERACTION, options);
|
||||
}
|
||||
}, options.throwAnimation?.delay?? 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize("OIF.Attack.Melee.Error.TooFar"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if the distance is within max distance
|
||||
if (options.distance < options.system.meleeWeaponDistance + options.gridUnitSize)
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Melee Attack
|
||||
////////////////////////////////////////////////////////////
|
||||
// Check if the animation should be played
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.USE_ANIMATIONS) && options.meleeAnimation != undefined)
|
||||
{
|
||||
// Define melee sequence to be played
|
||||
let SequenceIdentifier = `${options.name}-melee-${options.token.document._id}`;
|
||||
let SequenceEffect = new Sequence(OIF.ID)
|
||||
.effect()
|
||||
.file(options.meleeAnimation.source)
|
||||
.atLocation(options.token)
|
||||
.stretchTo(options.target)
|
||||
.name(SequenceIdentifier)
|
||||
|
||||
// Play melee sequence
|
||||
await SequenceEffect.play();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Melee Attack Interaction
|
||||
////////////////////////////////////////////////////////////
|
||||
setTimeout(() => {
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.USE_ANIMATIONS))
|
||||
{
|
||||
Hooks.call(OIF.HOOKS.WEAPON.MELEE.HIT.POST_ANIMATION, options);
|
||||
}
|
||||
|
||||
let DidInteract = false;
|
||||
// Check if poweful impact effect should be played
|
||||
if ((options.meleeAnimation.powerful || options.tags.includes('powerful')) && GeneralSettings.Get(OIF.SETTINGS.GENERAL.POWERFUL_IMPACT_SHAKE_EFFECT))
|
||||
{
|
||||
OIF_SOCKET.executeForEveryone('ScreenShake');
|
||||
DidInteract = true;
|
||||
}
|
||||
|
||||
if (DidInteract)
|
||||
{
|
||||
Hooks.call(OIF.HOOKS.WEAPON.MELEE.HIT.POST_INTERACTION, options);
|
||||
}
|
||||
}, options.meleeAnimation?.delay ?? 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize("OIF.Attack.Melee.Error.TooFar"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* * RangedWeaponSingleAttack
|
||||
* This is the function responsible for ranged weapon attacks with
|
||||
* single targets
|
||||
*/
|
||||
static async RangedWeaponSingleAttack(options)
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Preparation
|
||||
////////////////////////////////////////////////////////////
|
||||
// Validate and prepare for the workflow
|
||||
options = ItemAnimator.ValidateAndPrepare(options);
|
||||
if (options.stopWorkflow == true) { return; }
|
||||
|
||||
Hooks.call(OIF.HOOKS.WEAPON.RANGED.POST_PREPARE, options);
|
||||
|
||||
// Check if the distance is below the maximum distance
|
||||
if (options.distance <= options.system.longDistance)
|
||||
{
|
||||
// Check if item has ammo property
|
||||
if (options.system.isConsumeAmmo)
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Ranged Ammo Attack
|
||||
////////////////////////////////////////////////////////////
|
||||
// Check if item has ammo set
|
||||
if (options.system.ammoItem == undefined)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Attack.Ranged.Error.NoAmmo'));
|
||||
console.error('Could not find the ammunition item!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a copy of the ammo item
|
||||
options.ammoItemCopy = await options.token.actor.getEmbeddedDocument('Item', options.system.ammoItem);
|
||||
|
||||
// Check if animation should be played
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.USE_ANIMATIONS) && options.rangedAnimation != undefined)
|
||||
{
|
||||
// Define ranged sequence to be played
|
||||
let SequenceIdentifier = `${options.name}-ranged-${options.token.document._id}`;
|
||||
let SequenceEffect = new Sequence(OIF.ID)
|
||||
.effect()
|
||||
.file(options.rangedAnimation.source)
|
||||
.atLocation(options.token)
|
||||
.stretchTo(options.target)
|
||||
.name(SequenceIdentifier)
|
||||
.missed(options.miss ?? false)
|
||||
|
||||
// Play ranged sequence
|
||||
await SequenceEffect.play();
|
||||
|
||||
// Get where the effect landed
|
||||
options = await ItemAnimator.GetLandedPosAnimated(options, SequenceIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get a random place to simulate where the projectile landed
|
||||
options = await ItemAnimator.GetLandedPosRandomCorner(options);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Ranged Attack Interaction
|
||||
////////////////////////////////////////////////////////////
|
||||
setTimeout(() => {
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.USE_ANIMATIONS))
|
||||
{
|
||||
Hooks.call(OIF.HOOKS.WEAPON.RANGED.HIT.POST_ANIMATION, options);
|
||||
}
|
||||
|
||||
let DidInteract = false;
|
||||
// Check if the item should or not break
|
||||
if (!(Helpers.RandomMax(100) <= GeneralSettings.Get(OIF.SETTINGS.GENERAL.DEFAULT_AMMUNITION_DESTRUCTION_CHANCE)))
|
||||
{
|
||||
let CreateItemPileOnMiss = GeneralSettings.Get(OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_MISS);
|
||||
let CreateItemPileOnHit = GeneralSettings.Get(OIF.SETTINGS.GENERAL.CREATE_ITEM_PILES_ON_HIT);
|
||||
let AddAmmunitionToTargetInventory = GeneralSettings.Get(OIF.SETTINGS.GENERAL.ADD_AMMUNITION_TO_TARGET_INVENTORY);
|
||||
|
||||
let ShouldRemoveItem = false;
|
||||
|
||||
// Check if the attack missed and if a item pile should be created
|
||||
if (options.miss && CreateItemPileOnMiss)
|
||||
{
|
||||
// Setup item pile position
|
||||
let ItemPilePosition = {
|
||||
x: options.landedPosX,
|
||||
y: options.landedPosY,
|
||||
}
|
||||
|
||||
// Drop item
|
||||
ItemDropper.DropAt(options.ammoItemCopy, 1, ItemPilePosition, options.target.document.elevation);
|
||||
}
|
||||
else if (AddAmmunitionToTargetInventory)
|
||||
{
|
||||
// Add item to target's inventory
|
||||
InventoryManipulator.AddItem(options.target, options.ammoItemCopy, 1);
|
||||
}
|
||||
else if (CreateItemPileOnHit)
|
||||
{
|
||||
options = ItemAnimator.GetLandedPosRandomCorner(options);
|
||||
|
||||
// Setup item pile position
|
||||
let ItemPilePosition = {
|
||||
x: options.landedPosX,
|
||||
y: options.landedPosY,
|
||||
}
|
||||
|
||||
// Drop item
|
||||
ItemDropper.DropAt(options.ammoItemCopy, 1, ItemPilePosition, options.target.document.elevation);
|
||||
}
|
||||
|
||||
DidInteract = true;
|
||||
}
|
||||
|
||||
// Check if poweful impact effect should be played
|
||||
if ((options.rangedAnimation.powerful || options.tags.indexOf('powerful') > 0) && GeneralSettings.Get(OIF.SETTINGS.GENERAL.POWERFUL_IMPACT_SHAKE_EFFECT))
|
||||
{
|
||||
OIF_SOCKET.executeForEveryone('ScreenShake');
|
||||
DidInteract = true;
|
||||
}
|
||||
|
||||
if (DidInteract)
|
||||
{
|
||||
Hooks.call(OIF.HOOKS.WEAPON.RANGED.HIT.POST_INTERACTION, options);
|
||||
}
|
||||
}, options.rangedAnimation?.delay ?? 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Ranged Attack
|
||||
////////////////////////////////////////////////////////////
|
||||
// Check if animation should be played
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.USE_ANIMATIONS) && options.rangedAnimation != undefined)
|
||||
{
|
||||
// Define ranged sequence to be played
|
||||
let SequenceIdentifier = `${options.name}-ranged-${options.token.document._id}`;
|
||||
let SequenceEffect = new Sequence(OIF.ID)
|
||||
.effect()
|
||||
.file(options.rangedAnimation.source)
|
||||
.atLocation(options.token)
|
||||
.stretchTo(options.target)
|
||||
.name(SequenceIdentifier)
|
||||
.missed(options.miss ?? false)
|
||||
|
||||
// Play ranged sequence
|
||||
await SequenceEffect.play();
|
||||
|
||||
Hooks.call(OIF.HOOKS.WEAPON.RANGED.HIT.POST_ANIMATION, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Attack.Ranged.Error.TooFar'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
import { ObjectsInteractionsFXData } from "../data/ObjectsInteractionsFXData.js";
|
||||
import { ActorInventorSettings } from "../interface/ActorInventorSettings.js";
|
||||
|
||||
Hooks.on("oifReady", () => {
|
||||
Hooks.on("createToken", async (actor) => {
|
||||
if (game.user.isGM) {
|
||||
if (actor != null && actor != undefined && actor.actor.type == "npc")
|
||||
{
|
||||
actor = actor.actor;
|
||||
let Tags = ItemTags.Get(actor);
|
||||
|
||||
// Make sure we are checking for tags, not strange promises..
|
||||
if ((typeof Tags) == Array)
|
||||
{
|
||||
if (Tags.indexOf("generateCurrency") > -1 && ActorInventorSettings.Get(OIF.SETTINGS.ACTOR_INVENTOR.CURRENCY_GENERATOR))
|
||||
{
|
||||
let CpLocation = game.settings.get(OIF.ID, OIF.SETTINGS.ACTOR_INVENTOR.CP_LOCATION);
|
||||
let SpLocation = game.settings.get(OIF.ID, OIF.SETTINGS.ACTOR_INVENTOR.SP_LOCATION);
|
||||
let GpLocation = game.settings.get(OIF.ID, OIF.SETTINGS.ACTOR_INVENTOR.GP_LOCATION);
|
||||
let PpLocation = game.settings.get(OIF.ID, OIF.SETTINGS.ACTOR_INVENTOR.PP_LOCATION);
|
||||
let EpLocation = game.settings.get(OIF.ID, OIF.SETTINGS.ACTOR_INVENTOR.EP_LOCATION);
|
||||
|
||||
actor.update({
|
||||
[CpLocation]: Math.floor(Math.random() * (getProperty(actor, CpLocation) + 1)),
|
||||
[SpLocation]: Math.floor(Math.random() * (getProperty(actor, SpLocation) + 1)),
|
||||
[GpLocation]: Math.floor(Math.random() * (getProperty(actor, GpLocation) + 1)),
|
||||
[PpLocation]: Math.floor(Math.random() * (getProperty(actor, PpLocation) + 1)),
|
||||
[EpLocation]: Math.floor(Math.random() * (getProperty(actor, EpLocation) + 1))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js"
|
||||
|
||||
export class ObjectsInteractionsFXData
|
||||
{
|
||||
static async SaveMasterTags(data, name, path)
|
||||
{
|
||||
const NewFile = new File([JSON.stringify(data)], name, { type: 'application/json' });
|
||||
await FilePicker.upload(OIF.FILES.ORIGIN, path, NewFile, {});
|
||||
}
|
||||
|
||||
static async LoadJSON(path)
|
||||
{
|
||||
return await foundry.utils.fetchJsonWithTimeout(path);
|
||||
}
|
||||
|
||||
static async SaveJSON(data, name, path)
|
||||
{
|
||||
const NewFile = new File([JSON.stringify(data)], name, { type: 'application/json' });
|
||||
await FilePicker.upload(OIF.FILES.ORIGIN, path, NewFile, {});
|
||||
}
|
||||
|
||||
static async GetUserMasterTags(pack)
|
||||
{
|
||||
let UserPacks = await ObjectsInteractionsFXData.LoadJSON(`${OIF.FILES.DATA_FOLDERS.ROOT}/TagPacks.json`);
|
||||
return UserPacks[pack];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { SettingsSkeleton } from "../library/skeletons/SettingsSkeleton.js";
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
|
||||
export class ActorInventorSettings extends SettingsSkeleton {
|
||||
static get defaultOptions() {
|
||||
const DefaultOptions = super.defaultOptions;
|
||||
|
||||
const OverrideOptions = {
|
||||
id: 'oif-actor-inventor-settings',
|
||||
title: game.i18n.localize('OIF.Settings.ActorInventorSettings.Title'),
|
||||
};
|
||||
|
||||
const MergedOptions = foundry.utils.mergeObject(DefaultOptions, OverrideOptions);
|
||||
return MergedOptions;
|
||||
}
|
||||
|
||||
activateListeners(html)
|
||||
{
|
||||
super.activateListeners(html, 'oif-actor-inventor-settings');
|
||||
}
|
||||
|
||||
static Register(name, options)
|
||||
{
|
||||
SettingsSkeleton._protoRegister('oif-actor-inventor-settings', name, options);
|
||||
}
|
||||
|
||||
static Get(name)
|
||||
{
|
||||
return SettingsSkeleton._protoGet('oif-actor-inventor-settings', name);
|
||||
}
|
||||
|
||||
static UpdateSettings()
|
||||
{
|
||||
SettingsSkeleton._protoUpdateSettings('oif-actor-inventor-settings');
|
||||
}
|
||||
|
||||
getData(options)
|
||||
{
|
||||
return SettingsSkeleton._protoGetData('oif-actor-inventor-settings', options);
|
||||
}
|
||||
|
||||
async _updateObject(event, formData)
|
||||
{}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
import { ActorInventorSettings } from "./ActorInventorSettings.js";
|
||||
|
||||
import { SettingsSkeleton } from "../library/skeletons/SettingsSkeleton.js";
|
||||
|
||||
export class GeneralSettings extends SettingsSkeleton {
|
||||
static get defaultOptions() {
|
||||
const DefaultOptions = super.defaultOptions;
|
||||
|
||||
const OverrideOptions = {
|
||||
id: 'oif-general-settings',
|
||||
title: game.i18n.localize('OIF.Settings.GeneralSettings.Title')
|
||||
};
|
||||
|
||||
const MergedOptions = foundry.utils.mergeObject(DefaultOptions, OverrideOptions);
|
||||
return MergedOptions;
|
||||
}
|
||||
|
||||
activateListeners(html)
|
||||
{
|
||||
super.activateListeners(html, 'oif-general-settings');
|
||||
}
|
||||
|
||||
static Register(name, options)
|
||||
{
|
||||
SettingsSkeleton._protoRegister('oif-general-settings', name, options);
|
||||
}
|
||||
|
||||
static Get(name)
|
||||
{
|
||||
return SettingsSkeleton._protoGet('oif-general-settings', name);
|
||||
}
|
||||
|
||||
static UpdateSettings()
|
||||
{
|
||||
SettingsSkeleton._protoUpdateSettings('oif-general-settings');
|
||||
}
|
||||
|
||||
getData(options)
|
||||
{
|
||||
return SettingsSkeleton._protoGetData('oif-general-settings', options)
|
||||
}
|
||||
|
||||
async _updateObject(event, formData)
|
||||
{}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
import { MasterTagsSettings } from "./MasterTagsSettings.js";
|
||||
import { ConfigSkeleton } from "../library/skeletons/ConfigSkeleton.js";
|
||||
|
||||
export class MasterTagConfiguration extends ConfigSkeleton
|
||||
{
|
||||
static get defaultOptions() {
|
||||
const DefaultOptions = super.defaultOptions;
|
||||
|
||||
const OverrideOptions = {
|
||||
id: 'oif-master-tag-configuration',
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagConfiguration.Title'),
|
||||
tag: '',
|
||||
caller: null,
|
||||
updaterFunction: MasterTagsSettings.UpdateTag,
|
||||
};
|
||||
|
||||
const MergedOptions = foundry.utils.mergeObject(DefaultOptions, OverrideOptions);
|
||||
return MergedOptions;
|
||||
}
|
||||
|
||||
static MAX_DELAY = 3000;
|
||||
|
||||
getData(options)
|
||||
{
|
||||
let ReturnedConfiguration;
|
||||
let DefaultConfigurationHeader =
|
||||
[
|
||||
{
|
||||
disabled: false,
|
||||
name: 'enabled',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Enabled.Title',
|
||||
type: 'checkbox',
|
||||
value: this.options.configurationData.enabled,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'type',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Type.Title',
|
||||
type: 'dropdown',
|
||||
value: this.options.configurationData.type,
|
||||
choices:
|
||||
[
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Type.Choices.None',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Type.Choices.MeleeAttack',
|
||||
value: 'meleeAttack'
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Type.Choices.RangedAttack',
|
||||
value: 'rangedAttack'
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Type.Choices.Lighting',
|
||||
value: 'lighting'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
switch (this.options.configurationData.type)
|
||||
{
|
||||
case 'meleeAttack':
|
||||
case 'rangedAttack':
|
||||
ReturnedConfiguration =
|
||||
[
|
||||
...DefaultConfigurationHeader,
|
||||
{
|
||||
disabled: false,
|
||||
name: 'meleeAnimation.source',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Weapon.MeleeAnimation.Source.Title',
|
||||
type: 'string',
|
||||
value: this.options.configurationData?.meleeAnimation?.source ?? '',
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'meleeAnimation.delay',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Weapon.MeleeAnimation.Delay.Title',
|
||||
type: 'slider',
|
||||
value: this.options.configurationData?.meleeAnimation?.delay ?? 0,
|
||||
range:
|
||||
{
|
||||
min: 0,
|
||||
max: MasterTagConfiguration.MAX_DELAY,
|
||||
step: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'throwAnimation.source',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Weapon.ThrowAnimation.Source.Title',
|
||||
type: 'string',
|
||||
value: this.options.configurationData?.throwAnimation?.source ?? '',
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'throwAnimation.delay',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Weapon.ThrowAnimation.Delay.Title',
|
||||
type: 'slider',
|
||||
value: this.options.configurationData?.throwAnimation?.delay ?? 0,
|
||||
range:
|
||||
{
|
||||
min: 0,
|
||||
max: MasterTagConfiguration.MAX_DELAY,
|
||||
step: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'returnAnimation.source',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Weapon.ReturnAnimation.Source.Title',
|
||||
type: 'string',
|
||||
value: this.options.configurationData?.returnAnimation?.source ?? '',
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'returnAnimation.delay',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Weapon.ReturnAnimation.Delay.Title',
|
||||
type: 'slider',
|
||||
value: this.options.configurationData?.returnAnimation?.delay ?? 0,
|
||||
range:
|
||||
{
|
||||
min: 0,
|
||||
max: MasterTagConfiguration.MAX_DELAY,
|
||||
step: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'rangedAnimation.source',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Weapon.RangedAnimation.Source.Title',
|
||||
type: 'string',
|
||||
value: this.options.configurationData?.rangedAnimation?.source ?? '',
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'rangedAnimation.delay',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Weapon.RangedAnimation.Delay.Title',
|
||||
type: 'slider',
|
||||
value: this.options.configurationData?.rangedAnimation?.delay ?? 0,
|
||||
range:
|
||||
{
|
||||
min: 0,
|
||||
max: MasterTagConfiguration.MAX_DELAY,
|
||||
step: 1,
|
||||
},
|
||||
}
|
||||
]
|
||||
break;
|
||||
case 'lighting':
|
||||
ReturnedConfiguration =
|
||||
[
|
||||
...DefaultConfigurationHeader,
|
||||
{
|
||||
disabled: false,
|
||||
name: 'light.animationType',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Title',
|
||||
hint: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Hint',
|
||||
type: 'dropdown',
|
||||
value: this.options.configurationData?.light?.animationType ?? 'none',
|
||||
choices:
|
||||
[
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.None',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.Torch',
|
||||
value: 'torch',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.Pulse',
|
||||
value: 'pulse',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.Chroma',
|
||||
value: 'chroma',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.PulsingWave',
|
||||
value: 'wave',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.SwirlingFog',
|
||||
value: 'fog',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.Sunburst',
|
||||
value: 'sunburst',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.LightDome',
|
||||
value: 'dome',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.MysteriousEmanation',
|
||||
value: 'emanation',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.HexaDome',
|
||||
value: 'hexa',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.GhostlyLight',
|
||||
value: 'ghost',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.EnergyField',
|
||||
value: 'energy',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.RoilingMass',
|
||||
value: 'roiling',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.BlackHole',
|
||||
value: 'hole',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.Vortex',
|
||||
value: 'vortex',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.BewitchingWave',
|
||||
value: 'witchwave',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.SwirlingRainbow',
|
||||
value: 'rainbowswirl',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.RadialRainbow',
|
||||
value: 'radialrainbow',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.FairyLight',
|
||||
value: 'fairy',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.ForceGrid',
|
||||
value: 'grid',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.StarLight',
|
||||
value: 'starlight',
|
||||
},
|
||||
{
|
||||
name: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Type.Choices.SmokePatch',
|
||||
value: 'smokepatch',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'light.animationSpeed',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Speed.Title',
|
||||
hint: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Speed.Hint',
|
||||
type: 'slider',
|
||||
value: this.options.configurationData?.light?.animationSpeed ?? 0,
|
||||
range:
|
||||
{
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 1,
|
||||
}
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'light.animationIntensity',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Intensity.Title',
|
||||
hint: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Intensity.Hint',
|
||||
type: 'slider',
|
||||
value: this.options.configurationData?.light?.animationIntensity ?? 1,
|
||||
range:
|
||||
{
|
||||
min: 1,
|
||||
max: 10,
|
||||
step: 1,
|
||||
}
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'light.animationReverse',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Reverse.Title',
|
||||
hint: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Animation.Reverse.Hint',
|
||||
type: 'checkbox',
|
||||
value: this.options.configurationData?.light?.animationReverse ?? false,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'light.color',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Color.Title',
|
||||
type: 'color',
|
||||
value: this.options.configurationData?.light?.color ?? '#000000',
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'light.intensity',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Intensity.Title',
|
||||
type: 'slider',
|
||||
value: this.options.configurationData?.light?.intensity ?? 0.5,
|
||||
range:
|
||||
{
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
}
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'light.angle',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Angle.Title',
|
||||
type: 'slider',
|
||||
value: this.options.configurationData?.light?.angle ?? 360,
|
||||
range:
|
||||
{
|
||||
min: 0,
|
||||
max: 360,
|
||||
step: 1,
|
||||
}
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'icons.unlit',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Icons.Unlit.Title',
|
||||
hint: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Icons.Unlit.Hint',
|
||||
type: 'icon',
|
||||
value: this.options.configurationData?.icons?.unlit ?? '',
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
name: 'icons.lit',
|
||||
title: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Icons.Lit.Title',
|
||||
hint: 'OIF.Settings.MasterTagConfiguration.Options.Lighting.Icons.Lit.Hint',
|
||||
type: 'icon',
|
||||
value: this.options.configurationData?.icons?.lit ?? '',
|
||||
}
|
||||
]
|
||||
break;
|
||||
case 'special':
|
||||
ReturnedConfiguration =
|
||||
[
|
||||
]
|
||||
break;
|
||||
default:
|
||||
ReturnedConfiguration =
|
||||
{
|
||||
...DefaultConfigurationHeader,
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
data: this.options.configurationData,
|
||||
config: ReturnedConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
async activateListeners(html)
|
||||
{
|
||||
super.activateListeners(html);
|
||||
}
|
||||
|
||||
async _updateObject(event, formData)
|
||||
{}
|
||||
}
|
||||
@@ -0,0 +1,929 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
import { ObjectsInteractionsFXData as OIFD } from '../data/ObjectsInteractionsFXData.js';
|
||||
import { MasterTagConfiguration } from "./MasterTagConfiguration.js";
|
||||
import { SystemSupporter } from "../system/SystemSupporter.js";
|
||||
import { TagHandler } from "../tags/TagHandler.js";
|
||||
import { Debug as DBG } from "../library/Debug.js";
|
||||
|
||||
export class MasterTagsSettings extends FormApplication {
|
||||
static get defaultOptions() {
|
||||
const DefaultOptions = super.defaultOptions;
|
||||
|
||||
const OverrideOptions = {
|
||||
closeOnSubmit: false,
|
||||
height: 'auto',
|
||||
width: 600,
|
||||
id: 'oif-master-tags-settings',
|
||||
submitOnChange: true,
|
||||
template: OIF.TEMPLATES.MASTER_TAGS_SETTINGS,
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagsSettings.Title'),
|
||||
};
|
||||
|
||||
const MergedOptions = foundry.utils.mergeObject(DefaultOptions, OverrideOptions);
|
||||
return MergedOptions;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Data
|
||||
////////////////////////////////////////////////////////////
|
||||
static PackHeaders = {};
|
||||
static CurrentPackHeader = {};
|
||||
static Tags = {};
|
||||
static ResultTags = [];
|
||||
static Changed = false;
|
||||
|
||||
// Interface
|
||||
static SearchString = '';
|
||||
static FocusOn = 'input';
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Register the settings related to master tags
|
||||
////////////////////////////////////////////////////////////
|
||||
static async Register()
|
||||
{
|
||||
// Prepare the internal foundry setting
|
||||
let FoundrySettingOptions = {
|
||||
scope : 'world',
|
||||
default: '',
|
||||
config : false,
|
||||
};
|
||||
|
||||
// Register setting inside foundry
|
||||
game.settings.register(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK, FoundrySettingOptions);
|
||||
|
||||
// Assign the default tag pack
|
||||
let Setting = await game.settings.get(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK);
|
||||
if (Setting == '')
|
||||
{
|
||||
await game.settings.set(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK, await SystemSupporter.GetDefaultTagPack());
|
||||
Setting = await game.settings.get(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Load tag pack from object
|
||||
////////////////////////////////////////////////////////////
|
||||
static async LoadTagPackFromObject(data)
|
||||
{
|
||||
// Build the header for the pack
|
||||
let PackHeader =
|
||||
{
|
||||
id : data.id,
|
||||
name : data.name,
|
||||
default : data.default,
|
||||
}
|
||||
|
||||
// Verifies if the required modules are present
|
||||
if (data.requires != undefined)
|
||||
{
|
||||
data.requires.forEach((element) => {
|
||||
if (!game.modules.get(element)?.active)
|
||||
{
|
||||
// Disable the pack if the required modules are not present
|
||||
// and set the disabled message
|
||||
PackHeader.disabled = true;
|
||||
PackHeader.disabledMessage = game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.DisabledMessage').replace('${modules}', data.requires);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the pack is the selected one
|
||||
if (await game.settings.get(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK) == data.id)
|
||||
{
|
||||
PackHeader.selected = true;
|
||||
MasterTagsSettings.CurrentPackHeader = PackHeader;
|
||||
}
|
||||
|
||||
// Insert the pack into pack list
|
||||
MasterTagsSettings.PackHeaders[PackHeader.id] = PackHeader;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Load tag pack from file
|
||||
////////////////////////////////////////////////////////////
|
||||
static async LoadTagPackFromFile(file)
|
||||
{
|
||||
// Build the header for the pack
|
||||
let PackData = await OIFD.LoadJSON(file);
|
||||
let PackHeader =
|
||||
{
|
||||
location: file,
|
||||
id : PackData.id,
|
||||
name : PackData.name,
|
||||
default : PackData.default,
|
||||
}
|
||||
|
||||
// Verifies if the required modules are present
|
||||
if (PackData.requires != undefined)
|
||||
{
|
||||
PackData.requires.forEach((element) => {
|
||||
if (!game.modules.get(element)?.active)
|
||||
{
|
||||
// Disable the pack if the required modules are not present
|
||||
// and set the disabled message
|
||||
PackHeader.disabled = true;
|
||||
PackHeader.disabledMessage = game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.DisabledMessage').replace('${modules}', PackData.requires);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the pack is the selected one
|
||||
if (await game.settings.get(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK) == PackData.id)
|
||||
{
|
||||
PackHeader.selected = true;
|
||||
MasterTagsSettings.CurrentPackHeader = PackHeader;
|
||||
}
|
||||
|
||||
// Insert the pack into pack list
|
||||
MasterTagsSettings.PackHeaders[PackHeader.id] = PackHeader;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Load default tag packs
|
||||
////////////////////////////////////////////////////////////
|
||||
static async LoadDefaultTagPacks()
|
||||
{
|
||||
const DefaultTagPacks =
|
||||
[
|
||||
'Empty',
|
||||
'FantasyJB2AComplete',
|
||||
'FantasyJB2AFree',
|
||||
'FantasyNoAnimations'
|
||||
];
|
||||
|
||||
for (const path of DefaultTagPacks)
|
||||
{
|
||||
await MasterTagsSettings.LoadTagPackFromFile(`modules/${OIF.ID}/module/data/defaultTagPacks/${path}.json`);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Load tags from a registered pack
|
||||
////////////////////////////////////////////////////////////
|
||||
static async LoadTags(pack)
|
||||
{
|
||||
// Check if the pack exists
|
||||
if (MasterTagsSettings.PackHeaders[pack] == undefined)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.Load.Error.NotFound').replace('${pack}', pack));
|
||||
pack = "Empty"
|
||||
await game.settings.set(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK, pack);
|
||||
}
|
||||
|
||||
// Load the tags from the pack
|
||||
let Data = {};
|
||||
if (MasterTagsSettings.PackHeaders[pack].default)
|
||||
{
|
||||
// Load the tags from the default pack
|
||||
Data = await OIFD.LoadJSON(MasterTagsSettings.PackHeaders[pack].location);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load the tags from the user pack
|
||||
const UserPacks = await OIFD.LoadJSON(`${OIF.FILES.DATA_FOLDERS.ROOT}/TagPacks.json`);
|
||||
Data = UserPacks[pack];
|
||||
}
|
||||
|
||||
MasterTagsSettings.CurrentPackHeader = MasterTagsSettings.PackHeaders[pack];
|
||||
MasterTagsSettings.Tags = Data.tags;
|
||||
MasterTagsSettings.ResultTags = MasterTagsSettings.Tags;
|
||||
|
||||
await game.settings.set(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK, pack);
|
||||
|
||||
// Update the tags at the tag handler
|
||||
await TagHandler.UpdateTags(MasterTagsSettings.Tags);
|
||||
|
||||
// Set a flag to indicate that the tags have changed
|
||||
MasterTagsSettings.Changed = false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Updates a single tag
|
||||
////////////////////////////////////////////////////////////
|
||||
static async UpdateTag(tag, data)
|
||||
{
|
||||
// Update the tag
|
||||
MasterTagsSettings.Tags[tag] = data;
|
||||
MasterTagsSettings.ResultTags[tag] = data;
|
||||
|
||||
// Update the tags at the tag handler
|
||||
TagHandler.UpdateTags(MasterTagsSettings.Tags);
|
||||
|
||||
// Set a flag to indicate that the tags have changed
|
||||
MasterTagsSettings.Changed = true;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Save user packs
|
||||
////////////////////////////////////////////////////////////
|
||||
static async SaveUserPacks()
|
||||
{
|
||||
// Put all the packs into a single file
|
||||
let Data = {}
|
||||
for (const [key, value] of Object.entries(MasterTagsSettings.PackHeaders))
|
||||
{
|
||||
// Skip the default packs and the current pack
|
||||
if (!value.default && value.id != MasterTagsSettings.CurrentPackHeader.id)
|
||||
{
|
||||
let PackData = await OIFD.GetUserMasterTags(value.id);
|
||||
Data[PackData.id] = PackData;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the current pack data if it is not a default pack
|
||||
if (!MasterTagsSettings.CurrentPackHeader.default)
|
||||
{
|
||||
let CurrentPackData =
|
||||
{
|
||||
...MasterTagsSettings.CurrentPackHeader,
|
||||
tags: MasterTagsSettings.Tags
|
||||
}
|
||||
CurrentPackData.selected = undefined;
|
||||
CurrentPackData.disabled = undefined;
|
||||
CurrentPackData.disabledMessage = undefined;
|
||||
CurrentPackData.location = undefined;
|
||||
Data[MasterTagsSettings.CurrentPackHeader.id] = CurrentPackData;
|
||||
}
|
||||
|
||||
MasterTagsSettings.Changed = false;
|
||||
|
||||
// Save the file
|
||||
await OIFD.SaveJSON(Data, 'TagPacks.json', OIF.FILES.DATA_FOLDERS.ROOT);
|
||||
|
||||
// Propagate changes
|
||||
OIF_SOCKET.executeForOthers('LoadFromConfig');
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Load user packs
|
||||
////////////////////////////////////////////////////////////
|
||||
static async LoadUserPacks()
|
||||
{
|
||||
// Load the file
|
||||
let Data = await OIFD.LoadJSON(`${OIF.FILES.DATA_FOLDERS.ROOT}/TagPacks.json`);
|
||||
|
||||
// Iterate through all packs
|
||||
for (const [key, value] of Object.entries(Data))
|
||||
{
|
||||
let PackHeader =
|
||||
{
|
||||
id : value.id,
|
||||
name: value.name,
|
||||
selected: value.id == await game.settings.get(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK) ? true : undefined,
|
||||
}
|
||||
|
||||
MasterTagsSettings.PackHeaders[value.id] = PackHeader;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Load everything
|
||||
static async LoadFromConfig()
|
||||
{
|
||||
// Load default packs
|
||||
await MasterTagsSettings.LoadDefaultTagPacks();
|
||||
// Load user packs
|
||||
await MasterTagsSettings.LoadUserPacks();
|
||||
// Get the current tag pack
|
||||
let CurrentTagPack = await game.settings.get(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK);
|
||||
// Is the pack disabled?
|
||||
if (MasterTagsSettings.PackHeaders[CurrentTagPack]?.disabled)
|
||||
{
|
||||
// Reset to default
|
||||
MasterTagsSettings.PackHeaders[CurrentTagPack].selected = false;
|
||||
CurrentTagPack = 'Emtpy';
|
||||
}
|
||||
|
||||
// Load the tags
|
||||
await MasterTagsSettings.LoadTags(CurrentTagPack);
|
||||
|
||||
DBG.Log('Loaded tags from config', MasterTagsSettings.Tags);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Internal function that filters the tags
|
||||
////////////////////////////////////////////////////////////
|
||||
static async _filterTags()
|
||||
{
|
||||
let FilteredTags = Object.keys(MasterTagsSettings.Tags)
|
||||
.filter((key) => key == MasterTagsSettings.SearchString || key.toLowerCase().includes(MasterTagsSettings.SearchString.toLowerCase()))
|
||||
.reduce((obj, key) => {
|
||||
return Object.assign(obj, {
|
||||
[key]: MasterTagsSettings.Tags[key]
|
||||
});
|
||||
}, {});
|
||||
|
||||
return FilteredTags;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Internal function that creates a new pack
|
||||
////////////////////////////////////////////////////////////
|
||||
async _createNewPack(id, name, tags)
|
||||
{
|
||||
// Current pack is no longer selected
|
||||
MasterTagsSettings.CurrentPackHeader.selected = false;
|
||||
MasterTagsSettings.PackHeaders[MasterTagsSettings.CurrentPackHeader.id].selected = false;
|
||||
|
||||
// Create the new pack
|
||||
let NewTagPackHeader = {
|
||||
id : id,
|
||||
name : name,
|
||||
selected: true
|
||||
};
|
||||
|
||||
// Add the pack to the list
|
||||
MasterTagsSettings.PackHeaders[id] = NewTagPackHeader;
|
||||
MasterTagsSettings.CurrentPackHeader = NewTagPackHeader;
|
||||
MasterTagsSettings.Tags = tags;
|
||||
|
||||
// Update the tags at the tag handler
|
||||
TagHandler.UpdateTags(MasterTagsSettings.Tags);
|
||||
|
||||
// Set the new pack as the current pac
|
||||
await game.settings.set(OIF.ID, OIF.SETTINGS.MASTER_TAGS.CURRENT_TAG_PACK, id);
|
||||
|
||||
MasterTagsSettings.Changed = true;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle pack selection
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handlePackSelection(event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
Object.values(MasterTagsSettings.PackHeaders).forEach((element) =>
|
||||
{
|
||||
element.selected = element.id == ClickedElement.value ? true : false;
|
||||
});
|
||||
await MasterTagsSettings.LoadTags(ClickedElement.value);
|
||||
|
||||
// Propagate changes
|
||||
OIF_SOCKET.executeForOthers('LoadFromConfig');
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Load user packs
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTagSearching(event)
|
||||
{
|
||||
if (event.key == 'Enter')
|
||||
{
|
||||
MasterTagsSettings.SearchString = event.target.value;
|
||||
if (MasterTagsSettings.SearchString != '')
|
||||
{
|
||||
MasterTagsSettings.ResultTags = await MasterTagsSettings._filterTags();
|
||||
}
|
||||
else
|
||||
{
|
||||
MasterTagsSettings.ResultTags = MasterTagsSettings.Tags;
|
||||
}
|
||||
MasterTagsSettings.FocusOn = 'search';
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle tag creation
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTagCreation(event)
|
||||
{
|
||||
if (event.key == 'Enter')
|
||||
{
|
||||
let CurrentTag = event.target.value;
|
||||
if (MasterTagsSettings.Tags[CurrentTag] == undefined || MasterTagsSettings.Tags[CurrentTag] == null)
|
||||
{
|
||||
MasterTagsSettings.Tags[CurrentTag] = {
|
||||
enabled: false,
|
||||
masterDeco: true
|
||||
};
|
||||
MasterTagsSettings.ResultTags = await MasterTagsSettings._filterTags();
|
||||
TagHandler.UpdateTags(MasterTagsSettings.Tags);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.Creation.Error.Duplicated'))
|
||||
}
|
||||
MasterTagsSettings.FocusOn = 'input';
|
||||
MasterTagsSettings.Changed = true;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle tag configuration
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTagConfiguration(event)
|
||||
{
|
||||
let CurrentTagName = event.target.parentElement.parentElement.id;
|
||||
let CurrentTag = MasterTagsSettings.Tags[CurrentTagName];
|
||||
let ConfigurationInterface = new MasterTagConfiguration;
|
||||
ConfigurationInterface.render(true,
|
||||
{
|
||||
configurationData: CurrentTag,
|
||||
caller: this,
|
||||
dataNameAtSingleton: CurrentTagName
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle tag deletion
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTagDeletion(event)
|
||||
{
|
||||
let CurrentTag = event.target.parentElement.parentElement.id;
|
||||
let ConfirmDialog = Dialog.confirm({
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagsSettings.Deletion.Title').replace('${tag}', CurrentTag),
|
||||
content: `<p style="color:red">${game.i18n.localize('OIF.Settings.MasterTagsSettings.Deletion.Description')}</p`,
|
||||
no: async () =>
|
||||
{
|
||||
this.render();
|
||||
},
|
||||
yes: async () =>
|
||||
{
|
||||
delete MasterTagsSettings.Tags[CurrentTag];
|
||||
MasterTagsSettings.ResultTags = await MasterTagsSettings._filterTags();
|
||||
TagHandler.UpdateTags(MasterTagsSettings.Tags);
|
||||
MasterTagsSettings.Changed = true;
|
||||
this.render();
|
||||
},
|
||||
defaultYes: false
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle tag pack deletion
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTagPackDeletion(event)
|
||||
{
|
||||
let ConfirmDialog = Dialog.confirm({
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Delete.Title').replace('${pack}', MasterTagsSettings.CurrentPackHeader.name),
|
||||
content: `<p style="color:red">${game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Delete.Description')}</p`,
|
||||
no: async () =>
|
||||
{
|
||||
this.render();
|
||||
},
|
||||
yes: async () =>
|
||||
{
|
||||
delete MasterTagsSettings.PackHeaders[MasterTagsSettings.CurrentPackHeader.id];
|
||||
await MasterTagsSettings.LoadTags("Empty");
|
||||
await MasterTagsSettings.SaveUserPacks();
|
||||
this.render();
|
||||
},
|
||||
defaultYes: false
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle tag pack rename
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTagPackRenaming(event)
|
||||
{
|
||||
let NewPackDialog = new Dialog({
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Rename.Title'),
|
||||
content: `
|
||||
<label for="oif-pack-new-id">${game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Rename.Id')}</label>
|
||||
<input type="text" id="oif-pack-new-id" name="oif-pack-new-id" value="${MasterTagsSettings.CurrentPackHeader.id}">
|
||||
<label for="oif-pack-new-name">${game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Rename.Name')}</label>
|
||||
<input type="text" id="oif-pack-new-name" name="oif-pack-new-name" value="${MasterTagsSettings.CurrentPackHeader.name}">
|
||||
`,
|
||||
buttons: {
|
||||
rename: {
|
||||
icon: '<i class="fas fa-pen"></i>',
|
||||
label: game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Rename.Label'),
|
||||
callback: async (html) => {
|
||||
let NewPackID = html.find('#oif-pack-new-id').val();
|
||||
let NewPackName = html.find('#oif-pack-new-name').val();
|
||||
|
||||
if (NewPackID == '' || NewPackName == '')
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Rename.Error.EmptyValues'));
|
||||
}
|
||||
else if (NewPackID != MasterTagsSettings.CurrentPackHeader.id || NewPackName != MasterTagsSettings.CurrentPackHeader.name)
|
||||
{
|
||||
let DoesNameCollide = false;
|
||||
Object.values(MasterTagsSettings.PackHeaders).every((element) => {
|
||||
if (element.name == NewPackName)
|
||||
{
|
||||
DoesNameCollide = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
if (MasterTagsSettings.PackHeaders[NewPackID] != undefined && DoesNameCollide)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Rename.Error.BothAlreadyExist'));
|
||||
}
|
||||
else if (MasterTagsSettings.PackHeaders[NewPackID] != undefined)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Rename.Error.IDAlreadyExist'));
|
||||
}
|
||||
else if (DoesNameCollide)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Rename.Error.NameAlreadyExist'));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rename the pack
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
NewPackDialog.render(true);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle tag pack export
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTagPackExport(event)
|
||||
{
|
||||
let CurrentPackData =
|
||||
{
|
||||
...MasterTagsSettings.CurrentPackHeader,
|
||||
tags: MasterTagsSettings.Tags
|
||||
}
|
||||
CurrentPackData.selected = undefined;
|
||||
CurrentPackData.disabled = undefined;
|
||||
CurrentPackData.disabledMessage = undefined;
|
||||
CurrentPackData.location = undefined;
|
||||
saveDataToFile(JSON.stringify(CurrentPackData), 'application/json', `${MasterTagsSettings.CurrentPackHeader.name}.tagpack`);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle tag pack import
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTagPackImport(event)
|
||||
{
|
||||
// Create a file input element
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.type = 'file';
|
||||
// Listen for the file to be selected
|
||||
inputElement.addEventListener('change', async (event) =>
|
||||
{
|
||||
// Read the file
|
||||
const selectedFile = event.target.files[0];
|
||||
readTextFromFile(selectedFile).then(async (json) =>
|
||||
{
|
||||
// Insert the file header into the pack headers
|
||||
let Data = JSON.parse(json);
|
||||
|
||||
// Check if the pack already exists
|
||||
let DuplicatedID = false;
|
||||
let DuplicatedName = false;
|
||||
let ShouldImport = true;
|
||||
if (MasterTagsSettings.PackHeaders[Data.id] != undefined)
|
||||
{
|
||||
DuplicatedID = true;
|
||||
}
|
||||
|
||||
// Check if there is a pack with the same name
|
||||
for (let PackId in MasterTagsSettings.PackHeaders)
|
||||
{
|
||||
if (MasterTagsSettings.PackHeaders[PackId].name == Data.name)
|
||||
{
|
||||
DuplicatedName = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Pack already exists
|
||||
if (DuplicatedID && DuplicatedName)
|
||||
{
|
||||
// Ask the user if he wants to overwrite the pack
|
||||
let ConfirmDialog = await Dialog.confirm({
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Import.Duplicated.Title'),
|
||||
content: `<p style="color:red">${game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Import.Duplicated.Description').replace('${pack}', Data.name)}</p`,
|
||||
no: () =>
|
||||
{
|
||||
ShouldImport = false;
|
||||
},
|
||||
yes: () =>
|
||||
{
|
||||
ShouldImport = true;
|
||||
},
|
||||
defaultYes: false
|
||||
});
|
||||
}
|
||||
// Duplicated ID
|
||||
else if (DuplicatedID)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Import.Error.DuplicatedID'));
|
||||
return;
|
||||
}
|
||||
// Duplicated name
|
||||
else if (DuplicatedName)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.Pack.Import.Error.DuplicatedName'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Import the pack
|
||||
if (ShouldImport)
|
||||
{
|
||||
await MasterTagsSettings.LoadTagPackFromObject(JSON.parse(json));
|
||||
|
||||
// Unselects the current pack
|
||||
MasterTagsSettings.CurrentPackHeader.selected = undefined;
|
||||
MasterTagsSettings.PackHeaders[MasterTagsSettings.CurrentPackHeader.id].selected = undefined;
|
||||
|
||||
// Set the imported pack as the current pack
|
||||
MasterTagsSettings.CurrentPackHeader = MasterTagsSettings.PackHeaders[Data.id];
|
||||
MasterTagsSettings.CurrentPackHeader.selected = true;
|
||||
MasterTagsSettings.PackHeaders[Data.id].selected = true;
|
||||
|
||||
// Load the tags from the imported pack
|
||||
MasterTagsSettings.Tags = Data.tags;
|
||||
MasterTagsSettings.ResultTags = await MasterTagsSettings._filterTags();
|
||||
TagHandler.UpdateTags(MasterTagsSettings.Tags);
|
||||
|
||||
// Save the pack headers
|
||||
await MasterTagsSettings.SaveUserPacks();
|
||||
}
|
||||
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
inputElement.click();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle tag pack copy
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTagPackCopy(event)
|
||||
{
|
||||
let NewPackDialog = new Dialog({
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Title'),
|
||||
content: `
|
||||
<br>
|
||||
<label for="oif-pack-new-id">${game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Id')}</label>
|
||||
<input type="text" id="oif-pack-new-id" name="oif-pack-new-id">
|
||||
<label for="oif-pack-new-name">${game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Name')}</label>
|
||||
<input type="text" id="oif-pack-new-name" name="oif-pack-new-name">
|
||||
`,
|
||||
buttons: {
|
||||
create: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
label: game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Copy').replace('${pack}', MasterTagsSettings.CurrentPackHeader.name),
|
||||
callback: async (html) => {
|
||||
let NewPackID = html.find('#oif-pack-new-id').val();
|
||||
let NewPackName = html.find('#oif-pack-new-name').val();
|
||||
|
||||
if (NewPackID == '' || NewPackName == '')
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.EmptyValues'));
|
||||
}
|
||||
else
|
||||
{
|
||||
let DoesNameCollide = false;
|
||||
Object.values(MasterTagsSettings.PackHeaders).every((element) => {
|
||||
if (element.name == NewPackName)
|
||||
{
|
||||
DoesNameCollide = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
if (MasterTagsSettings.PackHeaders[NewPackID] != undefined && DoesNameCollide)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.BothAlreadyExist'));
|
||||
}
|
||||
else if (MasterTagsSettings.PackHeaders[NewPackID] != undefined)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.IDAlreadyExist'));
|
||||
}
|
||||
else if (DoesNameCollide)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.NameAlreadyExist'));
|
||||
}
|
||||
else
|
||||
{
|
||||
await this._createNewPack(NewPackID, NewPackName, MasterTagsSettings.Tags);
|
||||
await MasterTagsSettings.SaveUserPacks();
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
default: 'create'
|
||||
});
|
||||
NewPackDialog.render(true);
|
||||
}
|
||||
|
||||
async _handleTagPackCreate(event)
|
||||
{
|
||||
let NewPackDialog = new Dialog({
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Title'),
|
||||
content: `
|
||||
<p>${game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Description')}</p>
|
||||
<label for="oif-pack-new-id">${game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Id')}</label>
|
||||
<input type="text" id="oif-pack-new-id" name="oif-pack-new-id">
|
||||
<label for="oif-pack-new-name">${game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Name')}</label>
|
||||
<input type="text" id="oif-pack-new-name" name="oif-pack-new-name">
|
||||
<br>
|
||||
`,
|
||||
buttons: {
|
||||
create: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
label: game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Create'),
|
||||
callback: async (html) => {
|
||||
let NewPackID = html.find('#oif-pack-new-id').val();
|
||||
let NewPackName = html.find('#oif-pack-new-name').val();
|
||||
|
||||
if (NewPackID == '' || NewPackName == '')
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.EmptyValues'));
|
||||
}
|
||||
else
|
||||
{
|
||||
let DoesNameCollide = false;
|
||||
Object.values(MasterTagsSettings.PackHeaders).every((element) => {
|
||||
if (element.name == NewPackName)
|
||||
{
|
||||
DoesNameCollide = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
if (MasterTagsSettings.PackHeaders[NewPackID] != undefined && DoesNameCollide)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.BothAlreadyExist').replace('${pack}', MasterTagsSettings.CurrentPackHeader.name).replace('${id}', MasterTagsSettings.CurrentPackHeader.id));
|
||||
}
|
||||
else if (MasterTagsSettings.PackHeaders[NewPackID] != undefined)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.IDAlreadyExist').replace('${id}', MasterTagsSettings.CurrentPackHeader.id));
|
||||
}
|
||||
else if (DoesNameCollide)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.NameAlreadyExist').replace('${pack}', MasterTagsSettings.CurrentPackHeader.name));
|
||||
}
|
||||
else
|
||||
{
|
||||
await MasterTagsSettings.LoadTags('Empty');
|
||||
await this._createNewPack(NewPackID, NewPackName, MasterTagsSettings.Tags);
|
||||
await MasterTagsSettings.SaveUserPacks();
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
default: 'create'
|
||||
});
|
||||
NewPackDialog.render(true);
|
||||
}
|
||||
|
||||
async _handleTagPackDiscardChanges()
|
||||
{
|
||||
let ConfirmDialog = Dialog.confirm({
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagsSettings.Discard.Title'),
|
||||
content: `<p style="color:red">${game.i18n.localize('OIF.Settings.MasterTagsSettings.Discard.Description')}</p`,
|
||||
no: async () =>
|
||||
{
|
||||
this.render();
|
||||
},
|
||||
yes: async () =>
|
||||
{
|
||||
await MasterTagsSettings.LoadTags(MasterTagsSettings.CurrentPackHeader.id);
|
||||
this.render();
|
||||
},
|
||||
defaultYes: false
|
||||
});
|
||||
}
|
||||
|
||||
async _handleTagPackSaveChanges(event)
|
||||
{
|
||||
if (!MasterTagsSettings.Changed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (MasterTagsSettings.CurrentPackHeader.default)
|
||||
{
|
||||
let NewPackDialog = new Dialog({
|
||||
title: game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPackCopy.Title'),
|
||||
content: `
|
||||
<p>${game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPackCopy.Description')}</p>
|
||||
<br>
|
||||
<label for="oif-pack-new-id">${game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPackCopy.Id')}</label>
|
||||
<input type="text" id="oif-pack-new-id" name="oif-pack-new-id">
|
||||
<label for="oif-pack-new-name">${game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPackCopy.Name')}</label>
|
||||
<input type="text" id="oif-pack-new-name" name="oif-pack-new-name">
|
||||
<br>
|
||||
`,
|
||||
buttons: {
|
||||
create: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
label: game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPackCopy.Copy').replace('${pack}', MasterTagsSettings.CurrentPackHeader.name),
|
||||
callback: async (html) => {
|
||||
let NewPackID = html.find('#oif-pack-new-id').val();
|
||||
let NewPackName = html.find('#oif-pack-new-name').val();
|
||||
|
||||
if (NewPackID == '' || NewPackName == '')
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPackCopy.Error.EmptyValues'));
|
||||
}
|
||||
else
|
||||
{
|
||||
let DoesNameCollide = false;
|
||||
Object.values(MasterTagsSettings.PackHeaders).every((element) => {
|
||||
if (element.name == NewPackName)
|
||||
{
|
||||
DoesNameCollide = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
if (MasterTagsSettings.PackHeaders[NewPackID] != undefined && DoesNameCollide)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.BothAlreadyExist').replace('${pack}', MasterTagsSettings.CurrentPackHeader.name).replace('${id}', MasterTagsSettings.CurrentPackHeader.id));
|
||||
}
|
||||
else if (MasterTagsSettings.PackHeaders[NewPackID] != undefined)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.IDAlreadyExist').replace('${id}', MasterTagsSettings.CurrentPackHeader.id));
|
||||
}
|
||||
else if (DoesNameCollide)
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize('OIF.Settings.MasterTagsSettings.NewPack.Error.NameAlreadyExist').replace('${pack}', MasterTagsSettings.CurrentPackHeader.name));
|
||||
}
|
||||
else
|
||||
{
|
||||
await this._createNewPack(NewPackID, NewPackName, MasterTagsSettings.Tags);
|
||||
await MasterTagsSettings.SaveUserPacks();
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
default: 'create'
|
||||
});
|
||||
NewPackDialog.render(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await MasterTagsSettings.SaveUserPacks();
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
activateListeners(html)
|
||||
{
|
||||
super.activateListeners(html);
|
||||
|
||||
// Tags
|
||||
html.on('change' , 'select' , this._handlePackSelection.bind(this));
|
||||
html.on('keypress', 'input[class="oif-tag-input"]' , this._handleTagCreation.bind(this));
|
||||
html.on('keypress', 'input[class="oif-tag-search"]' , this._handleTagSearching.bind(this));
|
||||
html.on('click' , 'i[class="fas fa-wrench"]' , this._handleTagConfiguration.bind(this));
|
||||
html.on('click' , 'i[class="fas fa-times"]' , this._handleTagDeletion.bind(this));
|
||||
|
||||
// Tag Packs
|
||||
html.on('click', 'button[id="oif-delete-tag-pack"]' , this._handleTagPackDeletion.bind(this));
|
||||
html.on('click', 'button[id="oif-rename-tag-pack"]' , this._handleTagPackRenaming.bind(this));
|
||||
html.on('click', 'button[id="oif-export-tag-pack"]' , this._handleTagPackExport.bind(this));
|
||||
html.on('click', 'button[id="oif-import-tag-pack"]' , this._handleTagPackImport.bind(this));
|
||||
html.on('click', 'button[id="oif-copy-tag-pack"]' , this._handleTagPackCopy.bind(this));
|
||||
html.on('click', 'button[id="oif-create-tag-pack"]' , this._handleTagPackCreate.bind(this));
|
||||
|
||||
html.on('click', 'button[id="oif-tag-pack-discard-changes"]', this._handleTagPackDiscardChanges.bind(this));
|
||||
html.on('click', 'button[id="oif-tag-pack-save-changes"]' , this._handleTagPackSaveChanges.bind(this));
|
||||
|
||||
let SelectedElement;
|
||||
switch (MasterTagsSettings.FocusOn)
|
||||
{
|
||||
case 'input' : SelectedElement = html[0].querySelector('input[class="oif-tag-input"]'); break;
|
||||
case 'search': SelectedElement = html[0].querySelector('input[class="oif-tag-search"]'); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
let CursorEnd = SelectedElement.value.length ?? 0;
|
||||
SelectedElement?.setSelectionRange(CursorEnd, CursorEnd);
|
||||
SelectedElement?.focus();
|
||||
}
|
||||
|
||||
getData(options)
|
||||
{
|
||||
return {
|
||||
packs: MasterTagsSettings.PackHeaders,
|
||||
currentPack: MasterTagsSettings.CurrentPackHeader,
|
||||
search: MasterTagsSettings.SearchString != '' ? MasterTagsSettings.SearchString : undefined,
|
||||
tags: MasterTagsSettings.ResultTags,
|
||||
canSave: MasterTagsSettings.Changed,
|
||||
}
|
||||
}
|
||||
|
||||
async _updateObject(event, formData)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
// The credits of this code goes to kandashi and ChueyB
|
||||
// I've just copied the sections that OIF uses, I actually
|
||||
// have no idea how it works or why it works, in the future
|
||||
// I will improve this section of OIF, but for now
|
||||
// this is good enough
|
||||
//
|
||||
// Thank you kandashi and ChueyB :D
|
||||
////////////////////////////////////////////////////////////
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
|
||||
export class CanvasLayer extends InteractionLayer
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
if (game.release.generation == 10)
|
||||
{
|
||||
this.loadar = new PIXI.Loader();
|
||||
}
|
||||
|
||||
this.mouseInteractionManager = null;
|
||||
|
||||
this._interactiveChildren = false;
|
||||
this._dragging = false;
|
||||
|
||||
this.options = this.constructor.layerOptions;
|
||||
}
|
||||
|
||||
async _draw(options)
|
||||
{
|
||||
}
|
||||
|
||||
static ScreenShake()
|
||||
{
|
||||
let Intensity = 1;
|
||||
let a = 1 * Intensity;
|
||||
let b = 2 * Intensity;
|
||||
let c = 3 * Intensity;
|
||||
document.getElementById('board').animate([
|
||||
{ transform: `translate(${a}px, ${a}px) rotate(0deg)` },
|
||||
{ transform: `translate(-${a}px, -${b}px) rotate(-${a}deg)` },
|
||||
{ transform: `translate(-${c}px, 0px) rotate(${a}deg)` },
|
||||
{ transform: `translate(${c}px, ${b}px) rotate(0deg)` },
|
||||
{ transform: `translate(${a}px, -${a}px) rotate(${a}deg)` },
|
||||
{ transform: `translate(-${a}px, ${b}px) rotate(-${a}deg)` },
|
||||
{ transform: `translate(-${c}px, ${a}px) rotate(0deg)` },
|
||||
{ transform: `translate(${c}px, ${a}px) rotate(-${a}deg)` },
|
||||
{ transform: `translate(-${a}px, -${a}px) rotate(${a}deg)` },
|
||||
{ transform: `translate(${a}px, ${b}px) rotate(0deg)` },
|
||||
{ transform: `translate(${a}px, -${b}px) rotate(-${a}deg)` }
|
||||
],
|
||||
{
|
||||
duration: 500,
|
||||
iterations: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Hooks.on('init', () => {
|
||||
CONFIG.Canvas.layers['CanvasEffects'] = {
|
||||
group: 'interface',
|
||||
layerClass: CanvasLayer
|
||||
};
|
||||
})
|
||||
@@ -0,0 +1,116 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
|
||||
export class Debug
|
||||
{
|
||||
static Initialize()
|
||||
{
|
||||
Debug._enabled = game.settings.get(OIF.ID, OIF.SETTINGS.GENERAL.DEVELOPER_MODE);
|
||||
Debug._errorStack = {};
|
||||
Debug._logStack = {};
|
||||
Debug._warnStack = {};
|
||||
|
||||
Hooks.on(OIF.HOOKS.CHANGE_SETTINGS, (settings) =>
|
||||
{
|
||||
Debug._enabled = settings[OIF.SETTINGS.GENERAL.DEVELOPER_MODE].value;
|
||||
});
|
||||
}
|
||||
|
||||
static Error(...args)
|
||||
{
|
||||
if (Debug._enabled)
|
||||
{
|
||||
console.error('OIF DEBUG - ERROR - ', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
static Log(...args)
|
||||
{
|
||||
if (Debug._enabled)
|
||||
{
|
||||
console.log('OIF DEBUG - LOG - ', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
static Warn(...args)
|
||||
{
|
||||
if (Debug._enabled)
|
||||
{
|
||||
console.warn('OIF DEBUG - WARN - ', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
static ErrorStack(id, ...args)
|
||||
{
|
||||
if (Debug._enabled)
|
||||
{
|
||||
if (Debug._errorStack[id] == undefined)
|
||||
{
|
||||
Debug._errorStack[id] = [];
|
||||
}
|
||||
|
||||
Debug._errorStack[id].push(args);
|
||||
}
|
||||
}
|
||||
|
||||
static LogStack(id, ...args)
|
||||
{
|
||||
if (Debug._enabled)
|
||||
{
|
||||
if (Debug._logStack[id] == undefined)
|
||||
{
|
||||
Debug._logStack[id] = [];
|
||||
}
|
||||
|
||||
Debug._logStack[id].push(args);
|
||||
}
|
||||
}
|
||||
|
||||
static WarnStack(id, ...args)
|
||||
{
|
||||
if (Debug._enabled)
|
||||
{
|
||||
if (Debug._warnStack[id] == undefined)
|
||||
{
|
||||
Debug._warnStack[id] = [];
|
||||
}
|
||||
|
||||
Debug._warnStack[id].push(args);
|
||||
}
|
||||
}
|
||||
|
||||
static FlushErrorStack(id)
|
||||
{
|
||||
if (Debug._enabled)
|
||||
{
|
||||
if (Debug._errorStack[id] != undefined)
|
||||
{
|
||||
console.error('OIF DEBUG - ERROR STACK - ', id, ...Debug._errorStack[id]);
|
||||
Debug._errorStack[id] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FlushLogStack(id)
|
||||
{
|
||||
if (Debug._enabled)
|
||||
{
|
||||
if (Debug._logStack[id] != undefined)
|
||||
{
|
||||
console.log('OIF DEBUG - LOG STACK - ', id, ...Debug._logStack[id]);
|
||||
Debug._logStack[id] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FlushWarnStack(id)
|
||||
{
|
||||
if (Debug._enabled)
|
||||
{
|
||||
if (Debug._warnStack[id] != undefined)
|
||||
{
|
||||
console.warn('OIF DEBUG - WARN STACK - ', id, ...Debug._warnStack[id]);
|
||||
Debug._warnStack[id] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
|
||||
let Parts = {
|
||||
'oif.settings.name' : 'settings/Name.hbs',
|
||||
'oif.settings.checkbox': 'settings/Checkbox.hbs',
|
||||
'oif.settings.slider' : 'settings/Slider.hbs',
|
||||
'oif.settings.dropdown': 'settings/Dropdown.hbs',
|
||||
'oif.settings.string' : 'settings/String.hbs',
|
||||
|
||||
'oif.config.title' : 'config/Title.hbs',
|
||||
'oif.config.name' : 'config/Name.hbs',
|
||||
'oif.config.checkbox': 'config/Checkbox.hbs',
|
||||
'oif.config.slider' : 'config/Slider.hbs',
|
||||
'oif.config.dropdown': 'config/Dropdown.hbs',
|
||||
'oif.config.string' : 'config/String.hbs',
|
||||
'oif.config.color' : 'config/Color.hbs',
|
||||
'oif.config.icon' : 'config/Icon.hbs',
|
||||
|
||||
'oif.masterTag.lighting': 'masterTag/Lighting.hbs',
|
||||
|
||||
'oif.masterTag.special.powerful': 'masterTag/special/Powerful.hbs',
|
||||
'oif.masterTag.special.generateCurrency': 'masterTag/special/GenerateCurrency.hbs'
|
||||
}
|
||||
|
||||
Hooks.on('ready', () => {
|
||||
Object.keys(Parts).forEach((key) => {
|
||||
Parts[key] = `modules/${OIF.ID}/module/templates/parts/${Parts[key]}`
|
||||
});
|
||||
|
||||
loadTemplates(Parts);
|
||||
|
||||
Handlebars.registerHelper('masterTagAnimation', (origin, type) => {
|
||||
let Header = `
|
||||
<h3> ${game.i18n.localize('OIF.Settings.MasterTagConfiguration.Tag.' + type)} </h3>
|
||||
`;
|
||||
|
||||
let Body = `
|
||||
<label for="oif-${type}-animation"> ${game.i18n.localize('OIF.Settings.MasterTagConfiguration.Tag.Animation')} </label>
|
||||
<input class="oif-animation-input-text" name="oif-${type}-animation" type="text" ${origin?.source ? 'value="' + origin.source + '"' : ''}>
|
||||
|
||||
<label for="oif-${type}-delay"> ${game.i18n.localize('OIF.Settings.MasterTagConfiguration.Tag.Delay')} </label>
|
||||
<input class="oif-animation-input-text" name="oif-${type}-delay" type="text" ${origin?.delay ? 'value="' + origin.delay + '"' : ''}>
|
||||
`;
|
||||
|
||||
let Complete = (Header + Body);
|
||||
|
||||
return new Handlebars.SafeString(Complete);
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
export class Helpers
|
||||
{
|
||||
static RandomMax(max)
|
||||
{
|
||||
return Math.floor(Math.random() * max + 1);
|
||||
}
|
||||
|
||||
static RandomMinMax(min, max)
|
||||
{
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
static GetCornersOfToken(token)
|
||||
{
|
||||
const width = token.width * game.canvas.dimensions.size;
|
||||
const height = token.height * game.canvas.dimensions.size;
|
||||
const corners = [
|
||||
{
|
||||
x: token.x - width / 2,
|
||||
y: token.y - height / 2
|
||||
},
|
||||
{
|
||||
x: token.x + width / 2,
|
||||
y: token.y - height / 2
|
||||
},
|
||||
{
|
||||
x: token.x + width / 2,
|
||||
y: token.y + height / 2
|
||||
},
|
||||
{
|
||||
x: token.x - width / 2,
|
||||
y: token.y + height / 2
|
||||
}
|
||||
];
|
||||
|
||||
return corners;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
|
||||
export class InventoryManipulator
|
||||
{
|
||||
static async RemoveItem(owner, item, quantity)
|
||||
{
|
||||
// Check if the quantity is exact, if so delete the item from the inventory
|
||||
if (item.system.quantity == quantity)
|
||||
{
|
||||
// Remove the item from the inventory
|
||||
await owner.actor.deleteEmbeddedDocuments("Item", [item.id]);
|
||||
return true;
|
||||
}
|
||||
// More than "quantity" units of the item remaining
|
||||
else if (item.system.quantity > quantity)
|
||||
{
|
||||
// Remove "quantity" units from the item
|
||||
await owner.actor.updateEmbeddedDocuments("Item", [{_id: item.id, "system.quantity": item.system.quantity - quantity}]);
|
||||
return true;
|
||||
}
|
||||
// Not enough items to be removed
|
||||
else
|
||||
{
|
||||
console.error(`Could not remove item! Tried to remove ${quantity} while having only ${item.system.quantity}`)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static async AddItem(target, item, quantity)
|
||||
{
|
||||
if (quantity < 1)
|
||||
{
|
||||
console.error(`Could not add item! ${quantity} is negative and thus invalid`);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
let ItemCopy = item.toObject();
|
||||
ItemCopy.system.quantity = quantity;
|
||||
await target.actor.createEmbeddedDocuments("Item", [ItemCopy]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { GeneralSettings } from "../interface/GeneralSettings.js";
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
|
||||
export class ItemDropper
|
||||
{
|
||||
static async DropAt(item, quantity, position, elevation)
|
||||
{
|
||||
// Check for invalid quantity
|
||||
if (quantity < 1)
|
||||
{
|
||||
console.error(`Failed to drop item! ${quantity} is negative and thus invalid`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the item
|
||||
let ItemCopy = await item.toObject();
|
||||
ItemCopy.system.quantity = quantity;
|
||||
|
||||
// Create a ItemPile and get token reference
|
||||
let ItemPileOptions = {
|
||||
items: ItemCopy,
|
||||
pileActorName: false,
|
||||
position: position,
|
||||
tokenOverrides: {}
|
||||
}
|
||||
|
||||
// Minify the name of dropped items
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.MINIFY_ITEM_PILES_NAMES))
|
||||
{
|
||||
ItemPileOptions.tokenOverrides.name = '▲';
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPileOptions.tokenOverrides.name = ItemCopy.name;
|
||||
}
|
||||
|
||||
// Check if ItemPile should snap to grid
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.SNAP_CREATED_ITEM_PILES_TO_GRID))
|
||||
{
|
||||
ItemPileOptions.position = canvas.grid.getSnappedPosition(ItemPileOptions.position.x, ItemPileOptions.position.y);
|
||||
}
|
||||
|
||||
let ItemPileTokenUuid = await game.itempiles.API.createItemPile(ItemPileOptions);
|
||||
let ItemPileToken = await fromUuid(ItemPileTokenUuid.tokenUuid);
|
||||
|
||||
// Set ItemPile elevation if needed
|
||||
if (OIF.OPTIONAL_MODULES.LEVELS.active && GeneralSettings.Get(OIF.SETTINGS.GENERAL.SET_ELEVATION_OF_ITEM_PILES))
|
||||
{
|
||||
ItemPileToken.update({
|
||||
elevation: elevation ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
// Add a tag to the ItemPile to indicate it was created by OIF
|
||||
if (OIF.OPTIONAL_MODULES.TAGGER.active)
|
||||
{
|
||||
Tagger.addTags(ItemPileToken, [`${OIF.ID}-dropped-item-${item.id}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { CanvasLayer } from './CanvasEffects.js';
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
import { MasterTagsSettings } from '../interface/MasterTagsSettings.js';
|
||||
|
||||
let Socket;
|
||||
|
||||
Hooks.on('socketlib.ready', () =>
|
||||
{
|
||||
Socket = socketlib.registerModule(OIF.ID);
|
||||
|
||||
// Canvas effects
|
||||
Socket.register('ScreenShake', CanvasLayer.ScreenShake);
|
||||
|
||||
// Tags
|
||||
Socket.register('LoadFromConfig', MasterTagsSettings.LoadFromConfig);
|
||||
|
||||
window.OIF_SOCKET = Socket;
|
||||
})
|
||||
@@ -0,0 +1,201 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../ObjectsInteractionsFX.js";
|
||||
import { ObjectsInteractionsFXData as OIFD } from "../data/ObjectsInteractionsFXData.js";
|
||||
import { GeneralSettings } from "../interface/GeneralSettings.js";
|
||||
import { TagHandler } from "../tags/TagHandler.js";
|
||||
|
||||
export class TokenLightingManipulator
|
||||
{
|
||||
static LIGHT_SOURCE_ID = OIF.ID + "_light_source";
|
||||
|
||||
static async SetDefaultLightingOptions(options)
|
||||
{
|
||||
await options.token.document.update({
|
||||
"light.bright" : 0,
|
||||
"light.dim" : 0,
|
||||
"light.animation.type" : undefined,
|
||||
"light.animation.speed" : 5,
|
||||
"light.animation.intensity": 5,
|
||||
"light.animation.reverse" : false,
|
||||
"light.color" : "#000000",
|
||||
"light.alpha" : 0.5,
|
||||
"light.angle" : 360,
|
||||
});
|
||||
|
||||
await options.item?.update({
|
||||
"img": options?.icons?.unlit ?? options?.item?.img ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
static async SetLightingOptions(options)
|
||||
{
|
||||
await options.token.document.update({
|
||||
"light.bright" : options?.item?.system?.range?.value ?? 0,
|
||||
"light.dim" : options?.item?.system?.range?.long ?? 0,
|
||||
"light.animation.type" : options?.light?.animationType ?? undefined,
|
||||
"light.animation.speed" : options?.light?.animationSpeed ?? 5,
|
||||
"light.animation.intensity": options?.light?.animationIntensity ?? 5,
|
||||
"light.animation.reverse" : options?.light?.animationReverse ?? false,
|
||||
"light.color" : options?.light?.color ?? "#000000",
|
||||
"light.alpha" : options?.light?.intensity ?? 0.5,
|
||||
"light.angle" : options?.light?.angle ?? 360,
|
||||
});
|
||||
|
||||
if (options.item != undefined)
|
||||
{
|
||||
if (options.lit)
|
||||
{
|
||||
await options.item.update({
|
||||
"img": options?.icons?.lit ?? options?.item?.img ?? undefined,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await options.item.update({
|
||||
"img": options?.icons?.unlit ?? options?.item?.img ?? undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async SetLighting(options)
|
||||
{
|
||||
// Check if light_source tag is already set
|
||||
if (Tagger.hasTags(options.token, TokenLightingManipulator.LIGHT_SOURCE_ID))
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize("OIF.Item.Lighting.Error.AlreadySource"));
|
||||
console.error("Failed to set token lighting based on item! Token is already a light source");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Call hook
|
||||
Hooks.call(OIF.HOOKS.ITEM.LIGHTING.LIGHT.PRE, options);
|
||||
|
||||
// Set token light properties
|
||||
TokenLightingManipulator.SetLightingOptions(options);
|
||||
|
||||
// Add the light source tag
|
||||
await Tagger.addTags(options.token, [TokenLightingManipulator.LIGHT_SOURCE_ID, `${OIF.ID}_${options.name}`]);
|
||||
|
||||
// Call hook
|
||||
Hooks.call(OIF.HOOKS.ITEM.LIGHTING.LIGHT.POS, options);
|
||||
}
|
||||
}
|
||||
|
||||
static async RemoveLighting(options)
|
||||
{
|
||||
// Check if light_source tag is not set
|
||||
if (!Tagger.hasTags(options.token, TokenLightingManipulator.LIGHT_SOURCE_ID))
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize("OIF.Item.Lighting.Error.NotSource"));
|
||||
console.error("Failed to reset token lighting based on item! Token is not a light source");
|
||||
}
|
||||
else if (!Tagger.hasTags(options.token, `${OIF.ID}_${options.name}`))
|
||||
{
|
||||
ui.notifications.error(game.i18n.localize("OIF.Item.Lighting.Error.NotRightSource"));
|
||||
console.error("Failed to reset token lighting based on item! Specified item is not the one providing light");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Call hook
|
||||
Hooks.call(OIF.HOOKS.ITEM.LIGHTING.EXTINGUISH.PRE, options);
|
||||
|
||||
// Reset token light properties
|
||||
TokenLightingManipulator.SetDefaultLightingOptions(options);
|
||||
|
||||
// Remove the light source tag
|
||||
await Tagger.removeTags(options.token, [TokenLightingManipulator.LIGHT_SOURCE_ID, `${OIF.ID}_${options.name}`]);
|
||||
|
||||
// Call hook
|
||||
Hooks.call(OIF.HOOKS.ITEM.LIGHTING.EXTINGUISH.POS, options);
|
||||
}
|
||||
}
|
||||
|
||||
static async ToggleItemLighting(options)
|
||||
{
|
||||
Hooks.call(OIF.HOOKS.ITEM.LIGHTING.POST_PREPARE, options);
|
||||
|
||||
if (GeneralSettings.Get(OIF.SETTINGS.GENERAL.LIGHTING_ITEMS_AUTOMATION))
|
||||
{
|
||||
// Check if light_source tag is set
|
||||
if (Tagger.hasTags(options.token, TokenLightingManipulator.LIGHT_SOURCE_ID))
|
||||
{
|
||||
// Remove if set
|
||||
this.RemoveLighting({ ...options, lit: false});
|
||||
|
||||
Hooks.call(OIF.HOOKS.ITEM.LIGHTING.EXTINGUISH.POST_APPLY, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add if unset
|
||||
this.SetLighting({ ...options, lit: true });
|
||||
|
||||
Hooks.call(OIF.HOOKS.ITEM.LIGHTING.LIGHT.POST_APPLY, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async RemoveAllLighting()
|
||||
{
|
||||
let Tokens = Tagger.getByTag(TokenLightingManipulator.LIGHT_SOURCE_ID);
|
||||
|
||||
for (let Token of Tokens)
|
||||
{
|
||||
await TokenLightingManipulator.SetDefaultLightingOptions({ token: await canvas.tokens.get(Token.id) });
|
||||
|
||||
// Get the tags of the token
|
||||
let TokenTags = Tagger.getTags(Token);
|
||||
|
||||
// Remove the prefix
|
||||
TokenTags = TokenTags.map(tag => tag.replace(OIF.ID + "_", ""));
|
||||
|
||||
let LightingTags = Object.assign({}, TagHandler.Tags['Lighting']);
|
||||
for (let key in LightingTags)
|
||||
{
|
||||
if (LightingTags.hasOwnProperty(key))
|
||||
{
|
||||
LightingTags[key].name = key;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter the tags to only get the ones related to lighting
|
||||
let itemTags = {};
|
||||
for (let key in LightingTags)
|
||||
{
|
||||
if (LightingTags.hasOwnProperty(key))
|
||||
{
|
||||
let lightingTag = LightingTags[key];
|
||||
TokenTags.forEach((tokenTag) =>
|
||||
{
|
||||
if (lightingTag.name == tokenTag)
|
||||
{
|
||||
itemTags[lightingTag.name] = lightingTag;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get the lighting items
|
||||
for (let lightingTag of Object.values(itemTags))
|
||||
{
|
||||
for (let item of Token.actor.items)
|
||||
{
|
||||
// Get the tags of the item
|
||||
let Tags = itemTags.Get(item);
|
||||
|
||||
// Check if the item has the tag
|
||||
if (Tags.includes(lightingTag.name))
|
||||
{
|
||||
// Set the item icon to the unlit one
|
||||
item.update({ "img": lightingTag.icons.unlit });
|
||||
|
||||
// Remove the tag
|
||||
await Tagger.removeTags(Token, [`${OIF.ID}_${lightingTag.name}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the light source tag
|
||||
Tagger.removeTags(Token, [TokenLightingManipulator.LIGHT_SOURCE_ID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../../ObjectsInteractionsFX.js";
|
||||
|
||||
export class ConfigSkeleton extends FormApplication {
|
||||
static get defaultOptions() {
|
||||
const DefaultOptions = super.defaultOptions;
|
||||
|
||||
const OverrideOptions = {
|
||||
closeOnSubmit: false,
|
||||
height: 'auto',
|
||||
width: 600,
|
||||
submitOnChange: true,
|
||||
template: OIF.TEMPLATES.CONFIG_SKELETON,
|
||||
caller: null,
|
||||
updaterFunction: null,
|
||||
configurationData: {},
|
||||
dataNameAtSingleton: '',
|
||||
};
|
||||
|
||||
const MergedOptions = foundry.utils.mergeObject(DefaultOptions, OverrideOptions);
|
||||
return MergedOptions;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Get the data from the singleton
|
||||
////////////////////////////////////////////////////////////
|
||||
async _updateDataAtSingleton(topography, data)
|
||||
{
|
||||
// Separate string into array of strings using the dot
|
||||
// as separator
|
||||
let Location = topography.split('.');
|
||||
|
||||
let Data = this.options.configurationData;
|
||||
for (let i = 0; i < Location.length; i++)
|
||||
{
|
||||
let Piece = Location[i];
|
||||
if (!Data.hasOwnProperty(Piece))
|
||||
{
|
||||
Data[Piece] = {};
|
||||
}
|
||||
if (i == Location.length - 1)
|
||||
{
|
||||
Data[Piece] = data;
|
||||
}
|
||||
Data = Data[Piece];
|
||||
}
|
||||
|
||||
|
||||
return this.options.configurationData;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle checkbox change
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleCheckboxChange(event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
let Data = await this._updateDataAtSingleton(ClickedElement.name, ClickedElement.checked);
|
||||
|
||||
this.options.configurationData = Data;
|
||||
await this.options.updaterFunction(this.options.dataNameAtSingleton, Data);
|
||||
|
||||
this.render();
|
||||
this.options.caller?.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle slider change
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleSliderChange(event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
let Data = await this._updateDataAtSingleton(ClickedElement.name, ClickedElement.value);
|
||||
|
||||
this.options.configurationData = Data;
|
||||
await this.options.updaterFunction(this.options.dataNameAtSingleton, Data);
|
||||
|
||||
this.render();
|
||||
this.options.caller?.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle dropdown change
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleDropdownChange(event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
let Data = await this._updateDataAtSingleton(ClickedElement.name, ClickedElement.value);
|
||||
|
||||
this.options.configurationData = Data;
|
||||
await this.options.updaterFunction(this.options.dataNameAtSingleton, Data);
|
||||
|
||||
this.render();
|
||||
this.options.caller?.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle text change
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleTextChange(event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
let Data = await this._updateDataAtSingleton(ClickedElement.name, ClickedElement.value);
|
||||
|
||||
this.options.configurationData = Data;
|
||||
await this.options.updaterFunction(this.options.dataNameAtSingleton, Data);
|
||||
|
||||
this.render();
|
||||
this.options.caller?.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Handle color change
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleColorChange(event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
let Data = await this._updateDataAtSingleton(ClickedElement.name, ClickedElement.value);
|
||||
|
||||
this.options.configurationData = Data;
|
||||
await this.options.updaterFunction(this.options.dataNameAtSingleton, Data);
|
||||
|
||||
this.render();
|
||||
this.options.caller?.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Activate listeners
|
||||
////////////////////////////////////////////////////////////
|
||||
activateListeners(html)
|
||||
{
|
||||
super.activateListeners(html);
|
||||
|
||||
// Checkbox
|
||||
html.on('change', 'input[type="checkbox"]', this._handleCheckboxChange.bind(this));
|
||||
// Slider
|
||||
html.on('change', 'input[type="range"]', this._handleTextChange.bind(this));
|
||||
html.on('change', 'input[type="number"]', this._handleTextChange.bind(this));
|
||||
// String
|
||||
html.on('change', 'input[type="text"]', this._handleTextChange.bind(this));
|
||||
// Dropdown
|
||||
html.on('change', 'select', this._handleDropdownChange.bind(this));
|
||||
// Color
|
||||
html.on('change', 'input[type="color"]', this._handleColorChange.bind(this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
import { GeneralSettings } from "../../interface/GeneralSettings.js";
|
||||
import { ObjectsInteractionsFX as OIF } from "../../ObjectsInteractionsFX.js";
|
||||
|
||||
export class SettingsSkeleton extends FormApplication
|
||||
{
|
||||
static get defaultOptions() {
|
||||
const DefaultOptions = super.defaultOptions;
|
||||
|
||||
const OverrideOptions = {
|
||||
closeOnSubmit: false,
|
||||
height: 'auto',
|
||||
with: 600,
|
||||
submitOnChange: true,
|
||||
template: OIF.TEMPLATES.SETTINGS_SKELETON
|
||||
};
|
||||
|
||||
const MergedOptions = foundry.utils.mergeObject(DefaultOptions, OverrideOptions);
|
||||
return MergedOptions;
|
||||
}
|
||||
|
||||
static Settings = {}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Prototype function used to register custom settings
|
||||
////////////////////////////////////////////////////////////
|
||||
static _protoRegister(config, name, options)
|
||||
{
|
||||
if (SettingsSkeleton.Settings[config] == undefined)
|
||||
{
|
||||
SettingsSkeleton.Settings[config] = {};
|
||||
}
|
||||
|
||||
// Prepare the internal foundry setting options
|
||||
let FoundrySettingOptions = {
|
||||
name: options['name'],
|
||||
hint: options['hint'],
|
||||
scope: options['scope'],
|
||||
default: options['default'],
|
||||
config: false
|
||||
}
|
||||
|
||||
// Detect and convert custom type to be used by foundry
|
||||
if (options['type'] != undefined)
|
||||
{
|
||||
// Sets a value inside option, named after the type name, as true
|
||||
// This is used inside .hbs templates to select which option
|
||||
// display/editor will be shown
|
||||
options[options['type']] = true;
|
||||
|
||||
// Convert type to a foundry compatible
|
||||
switch (options['type'])
|
||||
{
|
||||
case 'checkbox':
|
||||
FoundrySettingOptions['type'] = Boolean;
|
||||
break;
|
||||
|
||||
case 'slider':
|
||||
FoundrySettingOptions['type'] = Number;
|
||||
FoundrySettingOptions['range'] = options['range'];
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
FoundrySettingOptions['type'] = String;
|
||||
break;
|
||||
|
||||
case 'dropdown':
|
||||
FoundrySettingOptions['type'] = String;
|
||||
FoundrySettingOptions['choices'] = options['choices'];
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error(`Cannot register setting, ${options['type']} is not a valid type`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Register setting inside foundry
|
||||
game.settings.register(OIF.ID, name, FoundrySettingOptions);
|
||||
|
||||
// Get currently set value to be displayed
|
||||
options['value'] = game.settings.get(OIF.ID, name);
|
||||
|
||||
// Dropdown types are handled differently
|
||||
if (options['type'] == 'dropdown')
|
||||
{
|
||||
// Iterate through the choices and set them as selected
|
||||
// or not
|
||||
options['choices'].forEach((element) => {
|
||||
if (element.value == options['value'])
|
||||
{
|
||||
element['selected'] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
element['selected'] = false;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Set the ID to be used as reference later
|
||||
options['referenceID'] = name;
|
||||
|
||||
// Check there are required modules
|
||||
let RequiredModule = options['requiredModule'];
|
||||
if (RequiredModule != undefined)
|
||||
{
|
||||
// Check if the required module is active
|
||||
if (!RequiredModule.active)
|
||||
{
|
||||
options['disabled'] = true;
|
||||
options['disabledMessage'] = game.i18n.localize('OIF.Settings.ModuleRequired').replace("${module}", `"${RequiredModule.name}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// Register inside OIF
|
||||
SettingsSkeleton.Settings[config][name] = options;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Prototype function used to get custom settings
|
||||
////////////////////////////////////////////////////////////
|
||||
static _protoGet(config, name)
|
||||
{
|
||||
let CurrentSetting = SettingsSkeleton.Settings[config][name];
|
||||
switch (CurrentSetting['type'])
|
||||
{
|
||||
case 'checkbox':
|
||||
return CurrentSetting['value'] && !CurrentSetting['disabled'];
|
||||
break;
|
||||
|
||||
case 'slider':
|
||||
case 'string':
|
||||
case 'dropdown':
|
||||
return CurrentSetting['disabled'] ? CurrentSetting['default'] : CurrentSetting['value'];
|
||||
break;
|
||||
|
||||
default:
|
||||
return CurrentSetting['value'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Internal function that checks if restart is required
|
||||
////////////////////////////////////////////////////////////
|
||||
async _checkForRestart(config, id)
|
||||
{
|
||||
if (SettingsSkeleton.Settings[config][id]['restart'] != null && SettingsSkeleton.Settings[config][id]['restart'] != undefined && SettingsSkeleton.Settings[config][id]['restart'])
|
||||
{
|
||||
SettingsSkeleton.Settings[config][id]['requiresRestart'] = true;
|
||||
SettingsSkeleton.Settings[config][id]['restartMessage'] = 'OIF.Settings.RestartRequired';
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Internal function for handling Checkbox interactions
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleCheckBoxInteraction(config, event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
game.settings.set(OIF.ID, ClickedElement.id, ClickedElement.checked);
|
||||
SettingsSkeleton.Settings[config][ClickedElement.id]['value'] = ClickedElement.checked;
|
||||
|
||||
this._checkForRestart(config, ClickedElement.id);
|
||||
SettingsSkeleton._protoUpdateSettings(config);
|
||||
this.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Internal function for handling Slider interactions
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleSliderInteraction(config, event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
game.settings.set(OIF.ID, ClickedElement.id, ClickedElement.value);
|
||||
|
||||
// Check value
|
||||
let CurrentValue = Number(ClickedElement.value);
|
||||
if (CurrentValue > ClickedElement.max)
|
||||
{
|
||||
CurrentValue = ClickedElement.max;
|
||||
}
|
||||
else if (CurrentValue < ClickedElement.min)
|
||||
{
|
||||
CurrentValue = ClickedElement.min;
|
||||
}
|
||||
|
||||
SettingsSkeleton.Settings[config][ClickedElement.id]['value'] = CurrentValue;
|
||||
|
||||
this._checkForRestart(config, ClickedElement.id);
|
||||
SettingsSkeleton._protoUpdateSettings(config);
|
||||
this.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Internal function for handling String interactions
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleStringInteraction(config, event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
game.settings.set(OIF.ID, ClickedElement.id, ClickedElement.value);
|
||||
SettingsSkeleton.Settings[config]['value'] = ClickedElement.value;
|
||||
|
||||
this._checkForRestart(config, ClickedElement.id);
|
||||
SettingsSkeleton._protoUpdateSettings(config);
|
||||
this.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Internal function for handling Dropdown interactions
|
||||
////////////////////////////////////////////////////////////
|
||||
async _handleDropdownInteraction(config, event)
|
||||
{
|
||||
let ClickedElement = $(event.currentTarget)[0];
|
||||
game.settings.set(OIF.ID, ClickedElement.id, ClickedElement.value);
|
||||
SettingsSkeleton.Settings[config][ClickedElement.id]['value'] = ClickedElement.value;
|
||||
|
||||
SettingsSkeleton.Settings[config][ClickedElement.id]['choices'].forEach(function(element)
|
||||
{
|
||||
element['selected'] = false;
|
||||
if (element.value == ClickedElement.value)
|
||||
{
|
||||
element['selected'] = true;
|
||||
}
|
||||
});
|
||||
|
||||
this._checkForRestart(config, ClickedElement.id);
|
||||
SettingsSkeleton._protoUpdateSettings(config);
|
||||
this.render();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Prototype function that updates settings
|
||||
////////////////////////////////////////////////////////////
|
||||
static _protoUpdateSettings(config)
|
||||
{
|
||||
for (const key in SettingsSkeleton.Settings[config])
|
||||
{
|
||||
// Check if current element has a dependecy
|
||||
let CurrentSetting = SettingsSkeleton.Settings[config][key];
|
||||
let CurrentDependencies = CurrentSetting['dependsOn'];
|
||||
let CurrentUndependencies = CurrentSetting['excludesOn'];
|
||||
|
||||
// Check if setting requires a module to be used
|
||||
let RequiredModule = CurrentSetting.requiredModule;
|
||||
if (RequiredModule != null && RequiredModule != undefined)
|
||||
{
|
||||
// Check if required module isn't loaded
|
||||
if (!RequiredModule.active)
|
||||
{
|
||||
CurrentSetting['disabled'] = true;
|
||||
CurrentSetting['disabledMessage'] = game.i18n.localize('OIF.Settings.ModuleRequired').replace("${module}", `"${RequiredModule.name}"`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if setting has dependencies
|
||||
let AllRequirementsMet = true;
|
||||
if (CurrentDependencies != null && CurrentDependencies != undefined)
|
||||
{
|
||||
// Loop through all dependencies to check if the are Boolean
|
||||
let BoolCheck = true;
|
||||
for (let index = 0; index < CurrentDependencies.length; index++)
|
||||
{
|
||||
// Check if the current dependency is a Boolean
|
||||
const CurrentDependency = CurrentDependencies[index];
|
||||
if (SettingsSkeleton.Settings[config][CurrentDependency]['type'] != 'checkbox')
|
||||
{
|
||||
BoolCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If one of the dependencies isn't a Boolean, skip the rest
|
||||
if (BoolCheck == false)
|
||||
{
|
||||
console.error(`OIF | ${config} | ${key} | Dependency isn't a Boolean`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the dependecies exist
|
||||
let CurrentRequirements = [];
|
||||
for (let index = 0; index < CurrentDependencies.length; index++)
|
||||
{
|
||||
// Check if the current dependency exists
|
||||
const CurrentDependency = CurrentDependencies[index];
|
||||
let CurrentRequirement = SettingsSkeleton.Settings[config][CurrentDependency];
|
||||
if (CurrentRequirement != null && CurrentRequirement != undefined)
|
||||
{
|
||||
CurrentRequirements.push(CurrentRequirement);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all dependencies are met
|
||||
for (let index = 0; index < CurrentRequirements.length; index++)
|
||||
{
|
||||
// Check if the current dependency is met
|
||||
const CurrentRequirement = CurrentRequirements[index];
|
||||
if (!SettingsSkeleton._protoGet(config, CurrentRequirement.referenceID))
|
||||
{
|
||||
AllRequirementsMet = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If all dependencies are met, enable the setting
|
||||
if (AllRequirementsMet)
|
||||
{
|
||||
CurrentSetting['disabled'] = false;
|
||||
CurrentSetting['disabledMessage'] = '';
|
||||
}
|
||||
// If not, disable the setting
|
||||
else
|
||||
{
|
||||
// Create a string with all dependencies, so the user knows which ones are missing
|
||||
// separeted all dependencies with a comma, except the last one, which is separeted with 'and'
|
||||
let DependenciesString = '';
|
||||
for (let index = 0; index < CurrentRequirements.length; index++)
|
||||
{
|
||||
const CurrentRequirement = CurrentRequirements[index];
|
||||
if (index == CurrentRequirements.length - 1)
|
||||
{
|
||||
DependenciesString += ` and "${game.i18n.localize(CurrentRequirement['name'])}"`;
|
||||
}
|
||||
else
|
||||
{
|
||||
DependenciesString += `"${game.i18n.localize(CurrentRequirement['name'])}", `;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable the setting and add a message
|
||||
CurrentSetting['disabled'] = true;
|
||||
CurrentSetting['disabledMessage'] = game.i18n.localize('OIF.Settings.OptionRequired').replace("${option}", DependenciesString);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if setting has undependencies
|
||||
if (CurrentUndependencies != null && CurrentUndependencies != undefined && AllRequirementsMet)
|
||||
{
|
||||
// Loop through all undependencies to check if the are Boolean
|
||||
let BoolCheck = true;
|
||||
for (let index = 0; index < CurrentUndependencies.length; index++)
|
||||
{
|
||||
// Check if the current undependency is a Boolean
|
||||
const CurrentUndependency = CurrentUndependencies[index];
|
||||
if (SettingsSkeleton.Settings[config][CurrentUndependency]['type'] != 'checkbox')
|
||||
{
|
||||
BoolCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If one of the undependencies isn't a Boolean, skip the rest
|
||||
if (BoolCheck == false)
|
||||
{
|
||||
console.error(`OIF | ${config} | ${key} | Undependency isn't a Boolean`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the undependecies exist
|
||||
let CurrentUnrequirements = [];
|
||||
for (let index = 0; index < CurrentUndependencies.length; index++)
|
||||
{
|
||||
// Check if the current undependency exists
|
||||
const CurrentUndependency = CurrentUndependencies[index];
|
||||
let CurrentRequirement = SettingsSkeleton.Settings[config][CurrentUndependency];
|
||||
if (CurrentRequirement != null && CurrentRequirement != undefined)
|
||||
{
|
||||
CurrentUnrequirements.push(CurrentRequirement);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all undependencies are met
|
||||
let AllUnrequirementsMet = true;
|
||||
for (let index = 0; index < CurrentUnrequirements.length; index++)
|
||||
{
|
||||
// Check if the current undependency is met
|
||||
const CurrentUnrequirement = CurrentUnrequirements[index];
|
||||
if (SettingsSkeleton._protoGet(config, CurrentUnrequirement.referenceID))
|
||||
{
|
||||
AllUnrequirementsMet = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If all undependencies are met, enable the setting
|
||||
if (AllUnrequirementsMet)
|
||||
{
|
||||
CurrentSetting['disabled'] = false;
|
||||
CurrentSetting['disabledMessage'] = '';
|
||||
}
|
||||
// If not, disable the setting
|
||||
else
|
||||
{
|
||||
// Create a string with all undependencies, so the user knows which ones are missing
|
||||
// separeted all undependencies with a comma, except the last one, which is separeted with 'and'
|
||||
let UndependenciesString = '';
|
||||
for (let index = 0; index < CurrentUnrequirements.length; index++)
|
||||
{
|
||||
const CurrentUnrequirement = CurrentUnrequirements[index];
|
||||
if (index == CurrentUnrequirements.length - 1)
|
||||
{
|
||||
UndependenciesString += ` and "${game.i18n.localize(CurrentUnrequirement['name'])}"`;
|
||||
}
|
||||
else
|
||||
{
|
||||
UndependenciesString += `"${game.i18n.localize(CurrentUnrequirement['name'])}", `;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable the setting and add a message
|
||||
CurrentSetting['disabled'] = true;
|
||||
CurrentSetting['disabledMessage'] = game.i18n.localize('OIF.Settings.OptionUnrequired').replace("${option}", UndependenciesString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Hooks.call(OIF.HOOKS.CHANGE_SETTINGS, SettingsSkeleton.Settings[config]);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Prototype function to get data for rendering .hbs
|
||||
////////////////////////////////////////////////////////////
|
||||
static _protoGetData(config, options)
|
||||
{
|
||||
// Only return settings that are in the 'world' scope
|
||||
// for GMs
|
||||
let ReturnedSettings = {};
|
||||
for (const key in SettingsSkeleton.Settings[config])
|
||||
{
|
||||
let CurrentSetting = SettingsSkeleton.Settings[config][key];
|
||||
if (CurrentSetting.scope == 'world')
|
||||
{
|
||||
if (game.user.isGM)
|
||||
{
|
||||
ReturnedSettings[key] = CurrentSetting;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ReturnedSettings[key] = CurrentSetting;
|
||||
}
|
||||
}
|
||||
|
||||
return { settings: ReturnedSettings }
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Activate listeners
|
||||
////////////////////////////////////////////////////////////
|
||||
activateListeners(html, config)
|
||||
{
|
||||
super.activateListeners(html);
|
||||
|
||||
// Checkbox
|
||||
html.on('change', 'input[type=checkbox]', this._handleCheckBoxInteraction.bind(this, config));
|
||||
// Slider
|
||||
html.on('change', 'input[type=range]', this._handleSliderInteraction.bind(this, config));
|
||||
html.on('change', 'input[type=number]', this._handleSliderInteraction.bind(this, config));
|
||||
// String
|
||||
html.on('change', 'input[type=text]', this._handleStringInteraction.bind(this, config));
|
||||
// Dropdown
|
||||
html.on('change', 'select', this._handleDropdownInteraction.bind(this, config));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { DnD5e } from './systems/dnd5e.js'
|
||||
import { Pf2e } from './systems/pf2e.js'
|
||||
|
||||
export class SystemSupporter
|
||||
{
|
||||
static Initialize()
|
||||
{
|
||||
switch (game.system.id)
|
||||
{
|
||||
case 'dnd5e': SystemSupporter._provider = DnD5e ; break;
|
||||
case 'pf2e' : SystemSupporter._provider = Pf2e ; break;
|
||||
}
|
||||
}
|
||||
|
||||
static _provider = {}
|
||||
|
||||
static GetDefaultTagPack() { return SystemSupporter._provider.GetDefaultTagPack() ; }
|
||||
|
||||
static GetDefaultHookAttack() { return SystemSupporter._provider.GetDefaultHookAttack() ; }
|
||||
static GetDefaultHookItem() { return SystemSupporter._provider.GetDefaultHookItem() ; }
|
||||
static GetPossibleHooksAttack() { return SystemSupporter._provider.GetPossibleHooksAttack(); }
|
||||
static GetPossibleHooksItem() { return SystemSupporter._provider.GetPossibleHooksItem() ; }
|
||||
|
||||
static async ExtractOptions(workflow, source, from) { return await SystemSupporter._provider.ExtractOptions(workflow, source, from); }
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../../ObjectsInteractionsFX.js";
|
||||
|
||||
import { Debug as DBG } from "../../library/Debug.js";
|
||||
|
||||
export class DnD5e
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Return the default hooks
|
||||
////////////////////////////////////////////////////////////
|
||||
static GetDefaultHookAttack() { return 'dnd5e.rollAttack'; }
|
||||
static GetDefaultHookItem() { return 'dnd5e.useItem' ; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Return the default tag pack
|
||||
////////////////////////////////////////////////////////////
|
||||
static GetDefaultTagPack()
|
||||
{
|
||||
if (game.modules.get('jb2a_patreon')?.active)
|
||||
{
|
||||
return 'DefaultFantasyTagsJB2AComplete';
|
||||
}
|
||||
else if (game.modules.get('JB2A_DnD5e')?.active)
|
||||
{
|
||||
return 'DefaultFantasyTagsJB2AFree';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'DefaultFantasyTagsNoAnimations';
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Return all the hooks that can be used to get attack info
|
||||
////////////////////////////////////////////////////////////
|
||||
static GetPossibleHooksAttack()
|
||||
{
|
||||
// System hooks
|
||||
const System =
|
||||
[
|
||||
{
|
||||
name : 'OIF.Settings.AttachHooks.Attack.DnD5eAfterAttackRoll.Label',
|
||||
value: 'dnd5e.rollAttack',
|
||||
},
|
||||
{
|
||||
name : 'OIF.Settings.AttachHooks.Attack.DnD5eAfterDamageRoll.Label',
|
||||
value: 'dnd5e.rollDamage',
|
||||
}
|
||||
]
|
||||
// MidiQOL hooks
|
||||
const MidiQOL =
|
||||
[
|
||||
{
|
||||
name : 'OIF.Settings.AttachHooks.Attack.MidiQOLAfterAttackRoll.Label',
|
||||
value: 'midi-qol.AttackRollComplete',
|
||||
disabled: !OIF.OPTIONAL_MODULES.MIDI_QOL?.active,
|
||||
disabledMessage: 'OIF.Settings.AttachHooks.Attack.MidiQOLAfterAttackRoll.DisabledMessage',
|
||||
},
|
||||
{
|
||||
name : 'OIF.Settings.AttachHooks.Attack.MidiQOLAfterDamageRoll.Label',
|
||||
value: 'midi-qol.DamageRollComplete',
|
||||
disabled: !OIF.OPTIONAL_MODULES.MIDI_QOL?.active,
|
||||
disabledMessage: 'OIF.Settings.AttachHooks.Attack.MidiQOLAfterDamageRoll.DisabledMessage',
|
||||
},
|
||||
{
|
||||
name : 'OIF.Settings.AttachHooks.Attack.MidiQOLAfterCompleteRoll.Label',
|
||||
value: 'midi-qol.RollComplete',
|
||||
disabled: !OIF.OPTIONAL_MODULES.MIDI_QOL?.active,
|
||||
disabledMessage: 'OIF.Settings.AttachHooks.Attack.MidiQOLAfterCompleteRoll.DisabledMessage',
|
||||
}
|
||||
]
|
||||
|
||||
return System.concat(MidiQOL);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Return all the hooks that can be used to get item info
|
||||
////////////////////////////////////////////////////////////
|
||||
static GetPossibleHooksItem()
|
||||
{
|
||||
// System hooks
|
||||
const System =
|
||||
[
|
||||
{
|
||||
name : 'OIF.Settings.AttachHooks.Item.DnD5AfterItemRoll.Label',
|
||||
value: 'dnd5e.useItem',
|
||||
}
|
||||
]
|
||||
|
||||
return System;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Function that is called when a hook is triggered, note
|
||||
// that this function is called for every hook, so it is
|
||||
// important to make sure the data we are extracting is the
|
||||
// one we want
|
||||
//
|
||||
// (for example, if we are using MidiQOL, we only want to
|
||||
// extract data from the MidiQOL hooks)
|
||||
////////////////////////////////////////////////////////////
|
||||
static async ExtractOptions(workflow, source, from)
|
||||
{
|
||||
// Check if MidiQOL is active and if the hook is the one from MidiQOL
|
||||
let ShouldUseMidiQOL = OIF.OPTIONAL_MODULES.MIDI_QOL?.active;
|
||||
ShouldUseMidiQOL = ShouldUseMidiQOL && source == 'attack';
|
||||
let UsedHook = from;
|
||||
let DidUseMidiHook = UsedHook == 'midi-qol.AttackRollComplete' || UsedHook == 'midi-qol.DamageRollComplete' || UsedHook == 'midi-qol.RollComplete';
|
||||
ShouldUseMidiQOL = ShouldUseMidiQOL && DidUseMidiHook;
|
||||
|
||||
if (ShouldUseMidiQOL)
|
||||
{
|
||||
DBG.Log('Using MidiQOL hook', source, from, workflow);
|
||||
|
||||
// Retrieve the item
|
||||
let Item = workflow[0].item;
|
||||
|
||||
// Retrieve the item's tags
|
||||
let Tags = ItemTags.Get(Item);
|
||||
|
||||
// Retrieve the token
|
||||
let Token = await canvas.tokens.get(workflow[0].tokenId);
|
||||
|
||||
// Retrieve the targets
|
||||
let Targets = Array.from(game.user.targets);
|
||||
let HitTargets = workflow[0].hitTargets;
|
||||
|
||||
// Special treatment
|
||||
let DidMiss;
|
||||
switch (source)
|
||||
{
|
||||
case 'attack':
|
||||
{
|
||||
// Did it miss?
|
||||
DidMiss = HitTargets.size === 0 ?? false;
|
||||
|
||||
break;
|
||||
}
|
||||
case 'item':
|
||||
{
|
||||
// Did it miss?
|
||||
DidMiss = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the options
|
||||
let Options =
|
||||
{
|
||||
name : Tags[0],
|
||||
item : Item,
|
||||
tags : Tags,
|
||||
actor : Token.actor,
|
||||
token : Token,
|
||||
targets : Targets,
|
||||
hitTargets: HitTargets,
|
||||
miss : HitTargets.size === 0 ?? false,
|
||||
type : source,
|
||||
dice :
|
||||
{
|
||||
roll : workflow[0].roll,
|
||||
total : workflow[0].attackTotal,
|
||||
critical: workflow[0].isCritical,
|
||||
fumble : workflow[0].isFumble,
|
||||
},
|
||||
system :
|
||||
{
|
||||
name : 'dnd5e',
|
||||
meleeWeaponDistance: canvas.dimensions.distance,
|
||||
normalDistance : Item.system.range.value,
|
||||
longDistance : Item.system.range.long,
|
||||
isThrowable : Item.system.properties?.thr,
|
||||
isConsumeAmmo : Item.system.properties?.amm,
|
||||
ammoItem : Item.system.consume?.target,
|
||||
},
|
||||
}
|
||||
|
||||
// Recalculate melee weapon distance if the item has the reach tag
|
||||
if (Item.system.properties?.rch) { Options.system.meleeWeaponDistance *= 2; }
|
||||
|
||||
return Options;
|
||||
}
|
||||
// If Midi QOL is not active
|
||||
else
|
||||
{
|
||||
DBG.Log('Using default hook', source, from, workflow);
|
||||
|
||||
// Retrieve the item
|
||||
let Item = workflow[0];
|
||||
|
||||
// Retrieve the item's tags
|
||||
let Tags = ItemTags.Get(Item);
|
||||
|
||||
// Retrieve the actor and token
|
||||
let Actor = Item.parent;
|
||||
let Token = Actor.token?.object ?? Actor.getActiveTokens()[0];
|
||||
|
||||
// Retrieve the targets
|
||||
let Targets = Array.from(game.user.targets);
|
||||
let HitTargets = Targets.filter(t => t.actor.system.attributes.ac.value <= workflow[1].total);
|
||||
|
||||
// Special treatment
|
||||
let RollTotal = undefined;
|
||||
let DidMiss = false;
|
||||
switch (source)
|
||||
{
|
||||
case 'attack':
|
||||
{
|
||||
// Did it miss?
|
||||
DidMiss = workflow[1].isCritical ? false : HitTargets.length === 0 ?? false;
|
||||
|
||||
// Retrieve the roll total
|
||||
let UsedDice = workflow[1].dice[0].results;
|
||||
UsedDice.filter((d) => d.active);
|
||||
RollTotal = UsedDice.reduce((a, b) => a + b.result, 0);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'item':
|
||||
{
|
||||
// Did it miss?
|
||||
DidMiss = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the options
|
||||
let Options =
|
||||
{
|
||||
name : Tags[0],
|
||||
item : Item,
|
||||
tags : Tags,
|
||||
actor : Actor,
|
||||
token : Token,
|
||||
targets : Targets,
|
||||
hitTargets: HitTargets,
|
||||
miss : DidMiss,
|
||||
type : source,
|
||||
dice :
|
||||
{
|
||||
roll : RollTotal,
|
||||
total : workflow[1]?._total,
|
||||
critical: workflow[1]?.isCritical,
|
||||
fumble : workflow[1]?.isFumble,
|
||||
},
|
||||
system :
|
||||
{
|
||||
name : 'dnd5e',
|
||||
meleeWeaponDistance: canvas.dimensions.distance,
|
||||
normalDistance : Item.system.range.value,
|
||||
longDistance : Item.system.range.long,
|
||||
isThrowable : Item.system.properties?.thr,
|
||||
isConsumeAmmo : Item.system.properties?.amm,
|
||||
ammoItem : Item.system.consume?.target,
|
||||
},
|
||||
}
|
||||
|
||||
// Recalculate melee weapon distance if the item has the reach tag
|
||||
if (Item.system.properties?.rch) { Options.system.meleeWeaponDistance *= 2; }
|
||||
|
||||
return Options;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { ObjectsInteractionsFX as OIF } from "../../ObjectsInteractionsFX.js";
|
||||
|
||||
import { Debug as DBG } from "../../library/Debug.js";
|
||||
|
||||
export class Pf2e
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Return the default hooks
|
||||
////////////////////////////////////////////////////////////
|
||||
static GetDefaultHookAttack() { return 'createChatMessage'; }
|
||||
static GetDefaultHookItem() { return 'createChatMessage'; }
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Return the default tag pack
|
||||
////////////////////////////////////////////////////////////
|
||||
static GetDefaultTagPack()
|
||||
{
|
||||
if (game.modules.get('jb2a_patreon')?.active)
|
||||
{
|
||||
return 'DefaultFantasyTagsJB2AComplete';
|
||||
}
|
||||
else if (game.modules.get('JB2A_DnD5e')?.active)
|
||||
{
|
||||
return 'DefaultFantasyTagsJB2AFree';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'DefaultFantasyTagsNoAnimations';
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Return all the hooks that can be used to get attack info
|
||||
////////////////////////////////////////////////////////////
|
||||
static GetPossibleHooksAttack()
|
||||
{
|
||||
// System hooks
|
||||
const System =
|
||||
[
|
||||
{
|
||||
name : 'OIF.Settings.AttachHooks.Attack.Pf2eAfterAttackRoll.Label',
|
||||
value: 'createChatMessage',
|
||||
}
|
||||
]
|
||||
|
||||
return System;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Return all the hooks that can be used to get item info
|
||||
////////////////////////////////////////////////////////////
|
||||
static GetPossibleHooksItem()
|
||||
{
|
||||
// System hooks
|
||||
const System =
|
||||
[
|
||||
{
|
||||
name : 'OIF.Settings.AttachHooks.Item.Pf2eAfterItemRoll.Label',
|
||||
value: 'createChatMessage',
|
||||
}
|
||||
]
|
||||
|
||||
return System;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Function that is called when a hook is triggered, note
|
||||
// that this function is called for every hook, so it is
|
||||
// important make sure the data we are extracting is the
|
||||
// one we want
|
||||
////////////////////////////////////////////////////////////
|
||||
static async ExtractOptions(workflow, source, from)
|
||||
{
|
||||
DBG.Log('Using default hook', source, from, workflow);
|
||||
|
||||
// Retrieve the item
|
||||
let Item = workflow[0].item;
|
||||
|
||||
// Retrieve the item's tags;
|
||||
let Tags = ItemTags.Get(Item);
|
||||
|
||||
// Retrieve the token
|
||||
let Token = canvas.tokens.get(workflow[0].token._id);
|
||||
|
||||
// Retrieve the targets
|
||||
let Targets = Array.from(game.user.targets);
|
||||
let HitTargets = Targets.filter((t) => true);
|
||||
|
||||
// Special treatment
|
||||
let RollTotal = undefined;
|
||||
let DidMiss = false;
|
||||
let DidCrit = false;
|
||||
let DidFumble = false;
|
||||
switch (source)
|
||||
{
|
||||
case 'attack':
|
||||
{
|
||||
// Did it miss?
|
||||
let Outcome = workflow[0].flags.pf2e.context?.outcome ?? "";
|
||||
switch (Outcome)
|
||||
{
|
||||
case 'criticalFailure': DidMiss = true ; DidFumble = true; break;
|
||||
case 'failure' : DidMiss = true ; break;
|
||||
case 'success' : DidMiss = false; break;
|
||||
case 'criticalSuccess': DidMiss = false; DidCrit = true; break;
|
||||
default : DidMiss = false; break;
|
||||
}
|
||||
|
||||
// Get the roll total
|
||||
RollTotal = workflow[0].rolls._total;
|
||||
|
||||
break;
|
||||
}
|
||||
case 'item':
|
||||
{
|
||||
// Did it miss?
|
||||
DidMiss = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let IsThrowable = false;
|
||||
let ThrowableDistance = undefined;
|
||||
Item.system.traits.value.forEach((trait) =>
|
||||
{
|
||||
if (trait == 'thrown')
|
||||
{
|
||||
IsThrowable = true;
|
||||
ThrowableDistance = Item.system.range * 7;
|
||||
}
|
||||
else if (trait.includes('thrown'))
|
||||
{
|
||||
IsThrowable = true;
|
||||
ThrowableDistance = parseInt(trait.split('-')[1]) * 7;
|
||||
}
|
||||
});
|
||||
|
||||
let Options =
|
||||
{
|
||||
name : Tags[0],
|
||||
item : Item,
|
||||
tags : Tags,
|
||||
actor : Token.actor,
|
||||
token : Token,
|
||||
targets : Targets,
|
||||
hitTargets: HitTargets,
|
||||
miss : DidMiss,
|
||||
type : source,
|
||||
dice :
|
||||
{
|
||||
roll : undefined,
|
||||
total : RollTotal,
|
||||
critical : DidCrit,
|
||||
fumble : DidFumble,
|
||||
},
|
||||
system :
|
||||
{
|
||||
name : 'pf2e',
|
||||
meleeWeaponDistance: canvas.dimensions.distance,
|
||||
normalDistance : IsThrowable ? canvas.dimensions.distance : Item.system.range,
|
||||
longDistance : IsThrowable ? ThrowableDistance : Item.system.range,
|
||||
isThrowable : IsThrowable,
|
||||
isConsumeAmmo : false,
|
||||
ammoItem : ''
|
||||
}
|
||||
}
|
||||
|
||||
return Options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import { ItemAnimator } from "../animation/ItemAnimator.js";
|
||||
import { Debug as DBG } from "../library/Debug.js";
|
||||
import { TokenLightingManipulator } from "../library/TokenLightingManipulator.js";
|
||||
|
||||
export class TagHandler
|
||||
{
|
||||
static Tags = {
|
||||
MeleeAttack : {},
|
||||
RangedAttack: {},
|
||||
Lighting : {}
|
||||
}
|
||||
|
||||
static async UpdateTags(tags)
|
||||
{
|
||||
Object.entries(tags).forEach((tag) => {
|
||||
switch (tag[1].type)
|
||||
{
|
||||
case 'meleeAttack' : TagHandler.Tags.MeleeAttack[tag[0]] = tag[1]; break;
|
||||
case 'rangedAttack': TagHandler.Tags.RangedAttack[tag[0]] = tag[1]; break;
|
||||
case 'lighting' : TagHandler.Tags.Lighting[tag[0]] = tag[1]; break;
|
||||
default: break;
|
||||
}
|
||||
});
|
||||
|
||||
DBG.Log('Tags got updated', TagHandler.Tags);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Clean Tags
|
||||
////////////////////////////////////////////////////////////
|
||||
static CleanTags(tags)
|
||||
{
|
||||
let CleanedTags = tags;
|
||||
// Remove the tags inside of MeleeAttack
|
||||
CleanedTags = CleanedTags.filter((tag) =>
|
||||
{
|
||||
return TagHandler.Tags.MeleeAttack[tag] == undefined;
|
||||
});
|
||||
// Remove the tags inside of RangedAttack
|
||||
CleanedTags = CleanedTags.filter((tag) =>
|
||||
{
|
||||
return TagHandler.Tags.RangedAttack[tag] == undefined;
|
||||
});
|
||||
// Remove the tags inside of Lighting
|
||||
CleanedTags = CleanedTags.filter((tag) =>
|
||||
{
|
||||
return TagHandler.Tags.Lighting[tag] == undefined;
|
||||
});
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Dispatch Tags
|
||||
////////////////////////////////////////////////////////////
|
||||
static async Dispatch(options)
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Iterate through all the tags trying to find the first
|
||||
// valid tag
|
||||
options.tags.every((tag) =>
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// Dispatch Melee Attack Tags
|
||||
////////////////////////////////////////////////////////////
|
||||
if (TagHandler.Tags.MeleeAttack[tag] != undefined && options.type == 'attack')
|
||||
{
|
||||
// Combine the options
|
||||
let MergedOptions =
|
||||
{
|
||||
...options,
|
||||
...TagHandler.Tags.MeleeAttack[tag]
|
||||
}
|
||||
// Check if the tag is enabled
|
||||
if (MergedOptions.enabled)
|
||||
{
|
||||
// Add the other tags
|
||||
MergedOptions.tags = options.tags;
|
||||
|
||||
// Execute the animation
|
||||
ItemAnimator.MeleeWeaponSingleAttack(MergedOptions);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
////////////////////////////////////////////////////////////
|
||||
// Dispatch Ranged Attack Tags
|
||||
////////////////////////////////////////////////////////////
|
||||
else if (TagHandler.Tags.RangedAttack[tag] != undefined && options.type == 'attack')
|
||||
{
|
||||
// Combine the options
|
||||
let MergedOptions =
|
||||
{
|
||||
...options,
|
||||
...TagHandler.Tags.RangedAttack[tag]
|
||||
}
|
||||
// Check if the tag is enabled
|
||||
if (MergedOptions.enabled)
|
||||
{
|
||||
// Add the other tags
|
||||
MergedOptions.tags = options.tags;
|
||||
|
||||
// Execute the animation
|
||||
ItemAnimator.RangedWeaponSingleAttack(MergedOptions);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
////////////////////////////////////////////////////////////
|
||||
// Dispatch Lighting Tags
|
||||
////////////////////////////////////////////////////////////
|
||||
else if (TagHandler.Tags.Lighting[tag] != undefined && options.type == 'item')
|
||||
{
|
||||
// Combine the option
|
||||
let MergedOptions =
|
||||
{
|
||||
...options,
|
||||
...TagHandler.Tags.Lighting[tag]
|
||||
}
|
||||
// Check if the tag is enabled
|
||||
if (MergedOptions.enabled)
|
||||
{
|
||||
// Add the other tags
|
||||
MergedOptions.tags = options.tags;
|
||||
|
||||
// Execute the animation
|
||||
TokenLightingManipulator.ToggleItemLighting(MergedOptions);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG.Log('Could not find tag', tag, 'on', TagHandler.Tags);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
}
|
||||
}
|
||||
108
src/modules/objects-interactions-fx/module/styles/ItemTags.css
Normal file
108
src/modules/objects-interactions-fx/module/styles/ItemTags.css
Normal file
@@ -0,0 +1,108 @@
|
||||
.item-tags-list {
|
||||
gap: 1em;
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.item-tags-list > li {
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.item-tags-list input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.item-tags-list-icon-button {
|
||||
align-self: center;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
line-height: normal;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.item-tags-list-icon-button > i {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.item-tags-list-icon-button:hover, .item-tags-list-icon-button:focus {
|
||||
box-shadow: none;
|
||||
text-shadow: 0 0 5px red;
|
||||
}
|
||||
|
||||
.oif-tag-container {
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
background-color: #FFFFFF33;
|
||||
border-width: 1px;
|
||||
border-color: #00000033;
|
||||
border-style: solid;
|
||||
border-radius: 1pt;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.oif-tag {
|
||||
margin: 1px;
|
||||
border-width: 1px;
|
||||
border-color: #585858;
|
||||
border-style: solid;
|
||||
padding: 3px;
|
||||
background-color: rgb(181, 235, 181);
|
||||
border-top-left-radius: 5pt;
|
||||
border-bottom-left-radius: 5pt;
|
||||
border-top-right-radius: 1pt;
|
||||
border-bottom-right-radius: 1pt;
|
||||
align-items: center;
|
||||
display: inline;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.2), inset 0 1px 1px #fff;
|
||||
}
|
||||
|
||||
.oif-master-tag {
|
||||
margin: 1px;
|
||||
border-width: 1px;
|
||||
border-color: #585858;
|
||||
border-style: solid;
|
||||
padding: 3px;
|
||||
background-color: rgb(202, 181, 235);
|
||||
border-top-left-radius: 5pt;
|
||||
border-bottom-left-radius: 5pt;
|
||||
border-top-right-radius: 1pt;
|
||||
border-bottom-right-radius: 1pt;
|
||||
align-items: center;
|
||||
display: inline;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.2), inset 0 1px 1px #fff;
|
||||
}
|
||||
|
||||
.oif-special-tag {
|
||||
background-color: rgb(235, 216, 181);
|
||||
}
|
||||
|
||||
.oif-melee-weapon-tag {
|
||||
background-color: rgb(241, 156, 99);
|
||||
}
|
||||
|
||||
.oif-ranged-weapon-tag {
|
||||
background-color: rgb(253, 92, 92);
|
||||
}
|
||||
|
||||
.oif-lighting-tag {
|
||||
background-color: rgb(97, 216, 252);
|
||||
}
|
||||
|
||||
.oif-disabled-tag {
|
||||
background-color: rgb(106, 103, 110);
|
||||
}
|
||||
|
||||
.oif-tag-input {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
width: 120px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.oif-red-button {
|
||||
background-color: rgb(253, 92, 92);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
.oif-settings-container {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.oif-checkbox-container {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.oif-slider-container {
|
||||
display: inline-flex;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.oif-text-container {
|
||||
display: inline-flex;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.oif-string-container {
|
||||
display: inline-flex;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.oif-dropdown-container {
|
||||
display: inline-flex;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.oif-color-container {
|
||||
display: inline-flex;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.oif-icon-container {
|
||||
display: inline-flex;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.oif-option-text-container {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.oif-option-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.oif-option-hint {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.oif-option-slider-container {
|
||||
display: flex;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.oif-option-dropdown-container {
|
||||
display: flex;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.oif-option-color-container {
|
||||
display: flex;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.oif-option-string-container {
|
||||
display: flex;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.oif-option-icon-container {
|
||||
display: flex;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.oif-option-icon-img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.oif-disabled-option-text {
|
||||
color: #ff0000;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<form>
|
||||
<div class="oif-settings-container">
|
||||
{{#each config}}
|
||||
{{#if (eq type "checkbox")}}
|
||||
{{> "oif.config.checkbox"}}
|
||||
{{/if}}
|
||||
{{#if (eq type "slider")}}
|
||||
{{> "oif.config.slider"}}
|
||||
{{/if}}
|
||||
{{#if (eq type "dropdown")}}
|
||||
{{> "oif.config.dropdown"}}
|
||||
{{/if}}
|
||||
{{#if (eq type "string")}}
|
||||
{{> "oif.config.string"}}
|
||||
{{/if}}
|
||||
{{#if (eq type "color")}}
|
||||
{{> "oif.config.color"}}
|
||||
{{/if}}
|
||||
{{#if (eq type "icon")}}
|
||||
{{> "oif.config.icon"}}
|
||||
{{/if}}
|
||||
<hr>
|
||||
{{/each}}
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,17 @@
|
||||
<form>
|
||||
<div class="oif-tag-container">
|
||||
{{#if itemTags}}
|
||||
{{#each itemTags}}
|
||||
<div class="oif-tag">
|
||||
<span>
|
||||
{{this}}
|
||||
</span>
|
||||
<a style="margin-left: 5px; margin-right: 5px;">
|
||||
<i class="fas fa-times" id="{{@index}}"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
<input class="oif-tag-input" autofocus placeholder="new tag"/>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,128 @@
|
||||
<form>
|
||||
<div class="oif-settings-container">
|
||||
<h2 align="center"> {{localize "OIF.Settings.MasterTagsSettings.Pack.Title"}} </h2>
|
||||
<label for="oif-selected-tag-pack"> {{localize "OIF.Settings.MasterTagsSettings.Pack.Select"}} </label>
|
||||
<select name="oif-selected-tag-pack" style="width: 570px;">
|
||||
{{#each packs}}
|
||||
<option value="{{id}}" {{#if selected}} selected {{/if}} {{#if disabled}} disabled {{/if}}>
|
||||
{{name}}
|
||||
{{#if disabled}} {{disabledMessage}} {{/if}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
|
||||
<span style="display: flex;">
|
||||
{{#unless currentPack.default}}
|
||||
<button type="button" id="oif-delete-tag-pack" class="oif-red-button" {{#if canSave}} disabled {{/if}}> <i class="fas fa-times"></i> {{localize "OIF.Settings.MasterTagsSettings.Pack.Delete.Label"}} </button>
|
||||
<!--button type="button" id="oif-rename-tag-pack" {{#if canSave}} disabled {{/if}}> <i class="fas fa-pen"></i> {{localize "OIF.Settings.MasterTagsSettings.Pack.Rename.Label"}} </button-->
|
||||
<button type="button" id="oif-export-tag-pack" {{#if canSave}} disabled {{/if}}> <i class="fas fa-arrow-down"></i> {{localize "OIF.Settings.MasterTagsSettings.Pack.Export"}} </button>
|
||||
{{/unless}}
|
||||
<button type="button" id="oif-import-tag-pack" {{#if canSave}} disabled {{/if}}> <i class="fas fa-arrow-up"></i> {{localize "OIF.Settings.MasterTagsSettings.Pack.Import.Title"}} </button>
|
||||
<button type="button" id="oif-copy-tag-pack" {{#if canSave}} disabled {{/if}}> <i class="fas fa-arrow-right"></i> {{localize "OIF.Settings.MasterTagsSettings.Pack.Copy"}} </button>
|
||||
<button type="button" id="oif-create-tag-pack" {{#if canSave}} disabled {{/if}}> <i class="fas fa-plus"></i> {{localize "OIF.Settings.MasterTagsSettings.Pack.Create"}} </button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 align="center"> {{localize "OIF.Settings.MasterTagsSettings.Label"}} </h2>
|
||||
<input class="oif-tag-search" type="text" style="width: 585px;" placeholder="Search.." {{#if search}} value="{{search}}" {{/if}}>
|
||||
<hr>
|
||||
<div class="oif-tag-container">
|
||||
{{#if tags}}
|
||||
{{#each tags}}
|
||||
|
||||
{{#if (eq type "special")}}
|
||||
<div class="oif-master-tag oif-special-tag" id="{{@key}}">
|
||||
<i class="fad fa-star"></i>
|
||||
<span >
|
||||
{{@key}}
|
||||
</span>
|
||||
<a style="margin-left: 5px; margin-right: 5px;">
|
||||
<i class="fas fa-wrench"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq type "meleeAttack")}}
|
||||
<div class="oif-master-tag oif-melee-weapon-tag {{#unless enabled}} oif-disabled-tag {{/unless}}" id="{{@key}}">
|
||||
<i class="fad fa-sword"></i>
|
||||
<span >
|
||||
{{@key}}
|
||||
</span>
|
||||
<a style="margin-left: 5px; margin-right: 5px;">
|
||||
<i class="fas fa-wrench"></i>
|
||||
</a>
|
||||
<a style="margin-right: 5px;">
|
||||
<i class="fas fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq type "rangedAttack")}}
|
||||
<div class="oif-master-tag oif-ranged-weapon-tag {{#unless enabled}} oif-disabled-tag {{/unless}}" id="{{@key}}">
|
||||
<i class="fad fa-bow-arrow"></i>
|
||||
<span >
|
||||
{{@key}}
|
||||
</span>
|
||||
<a style="margin-left: 5px; margin-right: 5px;">
|
||||
<i class="fas fa-wrench"></i>
|
||||
</a>
|
||||
<a style="margin-right: 5px;">
|
||||
<i class="fas fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq type "lighting")}}
|
||||
<div class="oif-master-tag oif-lighting-tag {{#unless enabled}} oif-disabled-tag {{/unless}}" id="{{@key}}">
|
||||
<i class="fad fa-lightbulb-on"></i>
|
||||
<span >
|
||||
{{@key}}
|
||||
</span>
|
||||
<a style="margin-left: 5px; margin-right: 5px;">
|
||||
<i class="fas fa-wrench"></i>
|
||||
</a>
|
||||
<a style="margin-right: 5px;">
|
||||
<i class="fas fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq type "none")}}
|
||||
<div class="oif-master-tag {{#unless enabled}} oif-disabled-tag {{/unless}}" id="{{@key}}">
|
||||
<span >
|
||||
{{@key}}
|
||||
</span>
|
||||
<a style="margin-left: 5px; margin-right: 5px;">
|
||||
<i class="fas fa-wrench"></i>
|
||||
</a>
|
||||
<a style="margin-right: 5px;">
|
||||
<i class="fas fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#unless type}}
|
||||
<div class="oif-master-tag {{#unless enabled}} oif-disabled-tag {{/unless}}" id="{{@key}}">
|
||||
<span >
|
||||
{{@key}}
|
||||
</span>
|
||||
<a style="margin-left: 5px; margin-right: 5px;">
|
||||
<i class="fas fa-wrench"></i>
|
||||
</a>
|
||||
<a style="margin-right: 5px;">
|
||||
<i class="fas fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
<input class="oif-tag-input" autofocus placeholder="new tag"/>
|
||||
</div>
|
||||
{{#if canSave}}
|
||||
<span style="display: flex;">
|
||||
<button type="button" id="oif-tag-pack-discard-changes" class="oif-red-button"> <i class="fas fa-times"></i>{{localize "OIF.Settings.MasterTagsSettings.DiscardChanges"}}</button>
|
||||
<button type="button" id="oif-tag-pack-save-changes"> <i class="fas fa-check"></i>{{localize "OIF.Settings.MasterTagsSettings.SaveChanges"}}</button>;
|
||||
</span>
|
||||
{{/if}}
|
||||
</form>
|
||||
@@ -0,0 +1,19 @@
|
||||
<form>
|
||||
<div class="oif-settings-container">
|
||||
{{#each settings}}
|
||||
{{#if checkbox}}
|
||||
{{> "oif.settings.checkbox"}}
|
||||
{{/if}}
|
||||
{{#if slider}}
|
||||
{{> "oif.settings.slider"}}
|
||||
{{/if}}
|
||||
{{#if dropdown}}
|
||||
{{> "oif.settings.dropdown"}}
|
||||
{{/if}}
|
||||
{{#if string}}
|
||||
{{> "oif.settings.string"}}
|
||||
{{/if}}
|
||||
<hr>
|
||||
{{/each}}
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="oif-checkbox-container {{#if disabled}} oif-disabled {{/if}}">
|
||||
<input name="{{name}}" type="checkbox" {{#if disabled}} disabled {{else}} {{#if value}} checked {{/if}} {{/if}}>
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.config.name"}}
|
||||
<p class="oif-option-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="oif-color-container {{#if disabled}} oif-disabled {{/if}}">
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.config.name"}}
|
||||
<div class="oif-option-color-container">
|
||||
<input name="{{name}}" style="margin-right: 15px;" type="color" value="{{value}}">
|
||||
<input name="{{name}}" type="text" value="{{value}}">
|
||||
</div>
|
||||
<p class="oif-config-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="oif-dropdown-container {{#if disabled}} oif-disabled {{/if}}">
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.config.name"}}
|
||||
<div class="oif-option-dropdown-container">
|
||||
<select name="{{name}}" id="{{referenceID}}" {{#if disabled}} disabled {{/if}}>
|
||||
{{#each choices}}
|
||||
<option value="{{value}}" {{#if (eq ../value value)}} selected {{/if}}> {{localize name}} </option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<p class="oif-option-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="oif-icon-container {{#if disabled}} oif-disabled {{/if}}">
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.config.name"}}
|
||||
<div class="oif-option-icon-container">
|
||||
<img class="oif-option-icon-img" src="{{value}}">
|
||||
{{filePicker target=name type="image"}}
|
||||
<input name="{{name}}" style="width: 455px;" type="text" value="{{value}}" id="{{referenceID}}" {{#if disabled}} disabled {{/if}}>
|
||||
</div>
|
||||
<p class="oif-config-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<label class="oif-option-title">
|
||||
<span>
|
||||
{{localize title}}
|
||||
{{#if disabled}}
|
||||
<i class="oif-disabled-option-text"> {{localize disabledMessage}} </i>
|
||||
{{/if}}
|
||||
</span>
|
||||
</label>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="oif-slider-container {{#if disabled}} oif-disabled {{/if}}">
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.config.name"}}
|
||||
<div class="oif-option-slider-container">
|
||||
<input name="{{name}}" style="width: 465px; margin-right: 15px;" type="range" min="{{range.min}}" max="{{range.max}}" step="{{range.step}}" value="{{value}}" {{#if disabled}} disabled {{/if}}>
|
||||
<input name="{{name}}" style="width: 50px;" type="number" min="{{range.min}}" max="{{range.max}}" step="{{range.step}}" value="{{value}}" {{#if disabled}} disabled {{/if}}>
|
||||
</div>
|
||||
<p class="oif-option-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="oif-string-container {{#if disabled}} oif-disabled {{/if}}">
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.config.name"}}
|
||||
<div class="oif-option-text-container">
|
||||
<input name="{{name}}" style="width: 530px; margin-right: 15px;" type="text" value="{{value}}" id="{{referenceID}}" {{#if disabled}} disabled {{/if}}>
|
||||
</div>
|
||||
<p class="oif-config-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<span class="oif-option-title">
|
||||
<span>
|
||||
{{localize title}}
|
||||
{{#if disabled}}
|
||||
<i class="oif-disabled-option-text"> {{localize disabledMessage}} </i>
|
||||
{{/if}}
|
||||
</span>
|
||||
</span>
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- Lighting -->
|
||||
<h3> Light </h3>
|
||||
<span>
|
||||
<label for="Color"> Color </label>
|
||||
<input type="color" name="Color" value="{{tag.light.color}}">
|
||||
</span>
|
||||
<hr>
|
||||
|
||||
<span>
|
||||
<label for="Alpha"> Alpha </label>
|
||||
<input type="number" name="Alpha" value="{{tag.light.alpha}}" min="0" max="1">
|
||||
</span>
|
||||
<hr>
|
||||
|
||||
<span>
|
||||
<label for="AnimationType"> Animation Type </label>
|
||||
<select name="AnimationType">
|
||||
<option value="none" {{#if (eq tag.light.animationType "none")}} selected {{/if}}> None </option>
|
||||
<option value="pulse" {{#if (eq tag.light.animationType "torch")}} selected {{/if}}> Torch </option>
|
||||
</select>
|
||||
</span>
|
||||
<hr>
|
||||
|
||||
<!-- Icons -->
|
||||
<h3> Icons </h3>
|
||||
<label for="LitIcon"> Lit </label>
|
||||
<span style="display: flex;">
|
||||
<input type="text" name="LitIcon" value="{{tag.icons.lit}}"/>
|
||||
{{filePicker type="image" target="LitIcon"}}
|
||||
</span>
|
||||
<hr>
|
||||
|
||||
<label for="UnitIcon"> Unlit </label>
|
||||
<span style="display: flex;">
|
||||
<input type="text" name="UnitIcon" value="{{tag.icons.unlit}}"/>
|
||||
{{filePicker type="image" target="UnitIcon"}}
|
||||
</span>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="oif-checkbox-container" {{#if disabled}} style="opacity: 0.5;" {{/if}}>
|
||||
<input type="checkbox" id="{{referenceID}}" {{#if disabled}} disabled {{else}} {{#if value}} checked {{/if}} {{/if}}>
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.settings.name"}}
|
||||
<p class="oif-option-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="oif-dropdown-container" {{#if disabled}} style="opacity: 0.5;;" {{/if}}>
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.settings.name"}}
|
||||
<div class="oif-option-dropdown-container">
|
||||
<select name="{{name}}" id="{{referenceID}}" {{#if disabled}} disabled {{/if}}>
|
||||
{{#each choices}}
|
||||
<option value="{{value}}" {{#if selected}} selected {{/if}} {{#if disabled}}disabled{{/if}}> {{localize name}} {{#if disabled}} {{localize disabledMessage}} {{/if}} </option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<p class="oif-option-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<label class="oif-option-title">
|
||||
<span>
|
||||
{{localize name}}
|
||||
{{#if disabled}}
|
||||
<i class="oif-disabled-option-text"> {{localize disabledMessage}} </i>
|
||||
{{/if}}
|
||||
{{#if requiresRestart}}
|
||||
<i class="oif-disabled-option-text"> {{localize restartMessage}} </i>
|
||||
{{/if}}
|
||||
</span>
|
||||
</label>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="oif-slider-container" {{#if disabled}} style="opacity: 0.5;" {{/if}}>
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.settings.name"}}
|
||||
<div class="oif-option-slider-container">
|
||||
<input style="width: 465px; margin-right: 15px;" type="range" min="{{range.min}}" max="{{range.max}}" step="{{range.step}}" value="{{value}}" id="{{referenceID}}" {{#if disabled}} disabled {{/if}}>
|
||||
<input style="width: 50px;" type="number" min="{{range.min}}" max="{{range.max}}" step="{{range.step}}" value="{{value}}" id="{{referenceID}}" {{#if disabled}} disabled {{/if}}>
|
||||
</div>
|
||||
<p class="oif-option-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="oif-string-container" {{#if disabled}} style="opcaity: 0.5;" {{/if}}>
|
||||
<div class="oif-option-text-container">
|
||||
{{> "oif.settings.name"}}
|
||||
<div class="oif-option-text-container">
|
||||
<input style="width: 565px; margin-right: 15px;" type="text" value="{{value}}" id="{{referenceID}}" {{#if disabled}} disabled {{/if}}>
|
||||
</div>
|
||||
<p class="oif-option-hint"> {{localize hint}} </p>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user