├── .dockerignore ├── .gitattributes ├── .github └── issue_template.md ├── .gitignore ├── Dockerfile ├── Dockerfile.dev ├── LICENSE ├── README.md ├── app ├── .htaccess ├── components │ ├── action-sequence.html │ ├── action-table.html │ ├── buffs.html │ ├── macros.html │ └── simulator-status.html ├── css │ ├── .gitkeep │ ├── app.css │ ├── base.css │ └── instructions.css ├── data │ ├── .htaccess │ ├── buffs │ │ ├── Meal.json │ │ └── Medicine.json │ └── recipedb │ │ ├── Alchemist.json │ │ ├── Armorer.json │ │ ├── Blacksmith.json │ │ ├── Carpenter.json │ │ ├── Culinarian.json │ │ ├── Goldsmith.json │ │ ├── Leatherworker.json │ │ ├── Weaver.json │ │ └── normalize.py ├── img │ ├── .gitkeep │ ├── .htaccess │ ├── actions │ │ ├── Alchemist │ │ │ ├── advancedTouch.png │ │ │ ├── basicSynth.png │ │ │ ├── basicSynth2.png │ │ │ ├── basicTouch.png │ │ │ ├── comfortZone.png │ │ │ ├── delicateSynthesis.png │ │ │ ├── focusedSynthesis.png │ │ │ ├── focusedTouch.png │ │ │ ├── groundwork.png │ │ │ ├── intensiveSynthesis.png │ │ │ ├── patientTouch.png │ │ │ ├── preciseTouch.png │ │ │ ├── preparatoryTouch.png │ │ │ ├── prudentTouch.png │ │ │ ├── standardSynthesis.png │ │ │ └── standardTouch.png │ │ ├── Armorer │ │ │ ├── advancedTouch.png │ │ │ ├── basicSynth.png │ │ │ ├── basicSynth2.png │ │ │ ├── basicTouch.png │ │ │ ├── delicateSynthesis.png │ │ │ ├── focusedSynthesis.png │ │ │ ├── focusedTouch.png │ │ │ ├── groundwork.png │ │ │ ├── intensiveSynthesis.png │ │ │ ├── patientTouch.png │ │ │ ├── pieceByPiece.png │ │ │ ├── preciseTouch.png │ │ │ ├── preparatoryTouch.png │ │ │ ├── prudentTouch.png │ │ │ ├── standardSynthesis.png │ │ │ └── standardTouch.png │ │ ├── Blacksmith │ │ │ ├── advancedTouch.png │ │ │ ├── basicSynth.png │ │ │ ├── basicSynth2.png │ │ │ ├── basicTouch.png │ │ │ ├── delicateSynthesis.png │ │ │ ├── focusedSynthesis.png │ │ │ ├── focusedTouch.png │ │ │ ├── groundwork.png │ │ │ ├── intensiveSynthesis.png │ │ │ ├── patientTouch.png │ │ │ ├── preciseTouch.png │ │ │ ├── preparatoryTouch.png │ │ │ ├── prudentTouch.png │ │ │ ├── standardSynthesis.png │ │ │ └── standardTouch.png │ │ ├── Carpenter │ │ │ ├── advancedTouch.png │ │ │ ├── basicSynth.png │ │ │ ├── basicSynth2.png │ │ │ ├── basicTouch.png │ │ │ ├── delicateSynthesis.png │ │ │ ├── focusedSynthesis.png │ │ │ ├── focusedTouch.png │ │ │ ├── groundwork.png │ │ │ ├── intensiveSynthesis.png │ │ │ ├── patientTouch.png │ │ │ ├── preciseTouch.png │ │ │ ├── preparatoryTouch.png │ │ │ ├── prudentTouch.png │ │ │ ├── standardSynthesis.png │ │ │ └── standardTouch.png │ │ ├── Culinarian │ │ │ ├── advancedTouch.png │ │ │ ├── basicSynth.png │ │ │ ├── basicSynth2.png │ │ │ ├── basicTouch.png │ │ │ ├── delicateSynthesis.png │ │ │ ├── focusedSynthesis.png │ │ │ ├── focusedTouch.png │ │ │ ├── groundwork.png │ │ │ ├── intensiveSynthesis.png │ │ │ ├── patientTouch.png │ │ │ ├── preciseTouch.png │ │ │ ├── preparatoryTouch.png │ │ │ ├── prudentTouch.png │ │ │ ├── standardSynthesis.png │ │ │ └── standardTouch.png │ │ ├── Goldsmith │ │ │ ├── advancedTouch.png │ │ │ ├── basicSynth.png │ │ │ ├── basicSynth2.png │ │ │ ├── basicTouch.png │ │ │ ├── delicateSynthesis.png │ │ │ ├── focusedSynthesis.png │ │ │ ├── focusedTouch.png │ │ │ ├── groundwork.png │ │ │ ├── intensiveSynthesis.png │ │ │ ├── patientTouch.png │ │ │ ├── preciseTouch.png │ │ │ ├── preparatoryTouch.png │ │ │ ├── prudentTouch.png │ │ │ ├── standardSynthesis.png │ │ │ └── standardTouch.png │ │ ├── Leatherworker │ │ │ ├── advancedTouch.png │ │ │ ├── basicSynth.png │ │ │ ├── basicSynth2.png │ │ │ ├── basicTouch.png │ │ │ ├── delicateSynthesis.png │ │ │ ├── focusedSynthesis.png │ │ │ ├── focusedTouch.png │ │ │ ├── groundwork.png │ │ │ ├── intensiveSynthesis.png │ │ │ ├── patientTouch.png │ │ │ ├── preciseTouch.png │ │ │ ├── preparatoryTouch.png │ │ │ ├── prudentTouch.png │ │ │ ├── standardSynthesis.png │ │ │ └── standardTouch.png │ │ ├── Weaver │ │ │ ├── advancedTouch.png │ │ │ ├── basicSynth.png │ │ │ ├── basicSynth2.png │ │ │ ├── basicTouch.png │ │ │ ├── carefulSynthesis2.png │ │ │ ├── delicateSynthesis.png │ │ │ ├── focusedSynthesis.png │ │ │ ├── focusedTouch.png │ │ │ ├── groundwork.png │ │ │ ├── intensiveSynthesis.png │ │ │ ├── patientTouch.png │ │ │ ├── preciseTouch.png │ │ │ ├── preparatoryTouch.png │ │ │ ├── prudentTouch.png │ │ │ ├── standardSynthesis.png │ │ │ └── standardTouch.png │ │ ├── brandOfTheElements.png │ │ ├── byregotsBlessing.png │ │ ├── byregotsMiracle.png │ │ ├── carefulSynthesis.png │ │ ├── carefulSynthesis3.png │ │ ├── empty.png │ │ ├── flawlessSynthesis.png │ │ ├── greatStrides.png │ │ ├── hastyTouch.png │ │ ├── hastyTouch2.png │ │ ├── heart.png │ │ ├── initialPreparations.png │ │ ├── innerQuiet.png │ │ ├── innovation.png │ │ ├── innovativeTouch.png │ │ ├── makersMark.png │ │ ├── manipulation.png │ │ ├── manipulation2.png │ │ ├── mastersMend.png │ │ ├── mastersMend2.png │ │ ├── muscleMemory.png │ │ ├── nameOfTheElements.png │ │ ├── nymeiasWheel.png │ │ ├── observe.png │ │ ├── rapidSynthesis.png │ │ ├── rapidSynthesis2.png │ │ ├── rapidSynthesis3.png │ │ ├── reclaim.png │ │ ├── reflect.png │ │ ├── satisfaction.png │ │ ├── specialtyRefurbish.png │ │ ├── specialtyReinforce.png │ │ ├── steadyHand.png │ │ ├── steadyHand2.png │ │ ├── strokeOfGenius.png │ │ ├── trainedEye.png │ │ ├── trainedHand.png │ │ ├── trainedInstinct.png │ │ ├── tricksOfTheTrade.png │ │ ├── unknown.png │ │ ├── unknown.svg │ │ ├── veneration.png │ │ ├── wasteNot.png │ │ ├── wasteNot2.png │ │ └── whistle.png │ ├── classes │ │ ├── Alchemist.png │ │ ├── Armorer.png │ │ ├── Blacksmith.png │ │ ├── Carpenter.png │ │ ├── Culinarian.png │ │ ├── Goldsmith.png │ │ ├── Leatherworker.png │ │ └── Weaver.png │ ├── glyphicons-halflings-white.png │ ├── glyphicons-halflings.png │ └── instructions │ │ ├── AvailableActions.png │ │ ├── AvailableActions_cn.jpg │ │ ├── AvailableActions_de.jpg │ │ ├── AvailableActions_en.jpg │ │ ├── AvailableActions_fr.jpg │ │ ├── AvailableActions_ja.jpg │ │ ├── AvailableActions_ko.jpg │ │ ├── CrafterDetails.png │ │ ├── CrafterDetails_cn.jpg │ │ ├── CrafterDetails_de.jpg │ │ ├── CrafterDetails_en.jpg │ │ ├── CrafterDetails_fr.jpg │ │ ├── CrafterDetails_ja.jpg │ │ ├── CrafterDetails_ko.jpg │ │ ├── SequenceEditor.png │ │ ├── SequenceEditor_cn.jpg │ │ ├── SequenceEditor_de.jpg │ │ ├── SequenceEditor_en.jpg │ │ ├── SequenceEditor_fr.jpg │ │ ├── SequenceEditor_ja.jpg │ │ ├── SequenceEditor_ko.jpg │ │ ├── SimulationLog.png │ │ ├── SimulationLog_cn.jpg │ │ ├── SimulationLog_de.jpg │ │ ├── SimulationLog_en.jpg │ │ ├── SimulationLog_fr.jpg │ │ ├── SimulationLog_ja.jpg │ │ ├── SimulationLog_ko.jpg │ │ ├── Simulator.png │ │ ├── Simulator_cn.jpg │ │ ├── Simulator_de.jpg │ │ ├── Simulator_en.jpg │ │ ├── Simulator_fr.jpg │ │ ├── Simulator_ja.jpg │ │ ├── Simulator_ko.jpg │ │ ├── StatBonuses.png │ │ ├── SynthDetails.png │ │ ├── SynthDetails_cn.jpg │ │ ├── SynthDetails_de.jpg │ │ ├── SynthDetails_en.jpg │ │ ├── SynthDetails_fr.jpg │ │ ├── SynthDetails_ja.jpg │ │ └── SynthDetails_ko.jpg ├── index.html ├── js │ ├── actions.js │ ├── app.js │ ├── components │ │ ├── action-sequence.js │ │ ├── action-table.js │ │ ├── buffs.js │ │ ├── dropzone.js │ │ ├── macros.js │ │ └── simulator-status.js │ ├── controllers │ │ ├── charimport.js │ │ ├── crafterstats.js │ │ ├── macroimport.js │ │ ├── main.js │ │ ├── options.js │ │ ├── recipesearch.js │ │ ├── sequenceeditor.js │ │ ├── settingsimport.js │ │ ├── simulator.js │ │ └── solver.js │ ├── directives.js │ ├── ffxivcraftmodel.js │ ├── polyfills.js │ ├── routes.js │ ├── seededrandom.js │ ├── services │ │ ├── actions.js │ │ ├── bonusstats.js │ │ ├── buffsdb.js │ │ ├── locale.js │ │ ├── localstorage.js │ │ ├── profile.js │ │ ├── recipelibrary.js │ │ ├── simulator.js │ │ ├── tooltips.js │ │ └── translatelocalstorage.js │ ├── simulationworker.js │ └── solver │ │ ├── eacomplex.js │ │ ├── easimple.js │ │ ├── service.js │ │ └── worker.js ├── lib │ ├── lvl-drag-drop │ │ ├── LICENSE │ │ ├── README.md │ │ ├── lvl-drag-drop.js │ │ └── lvl-uuid.js │ ├── string │ │ └── String.js │ └── yagal │ │ ├── algorithms.js │ │ ├── creator.js │ │ ├── fitness.js │ │ ├── toolbox.js │ │ └── tools.js ├── locale │ ├── ar.json │ ├── cn.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── ja.json │ └── ko.json ├── modals │ ├── charimport.html │ ├── macroimport.html │ ├── options.html │ └── settingsimport.html └── views │ ├── about.html │ ├── crafter-attributes.html │ ├── instructions.html │ ├── simulator.html │ └── solver.html ├── package.json ├── scripts ├── export_settings.js ├── extract_recipes.py └── import_settings.js ├── server.js ├── start-browser-sync.cmd └── webserver.cmd /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.png binary 3 | *.html text eol=lf 4 | *.css text eol=lf 5 | *.js text eol=lf 6 | *.json text eol=lf 7 | *.cmd text eol=crlf 8 | .htaccess text eol=lf 9 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ## Required Information 2 | 3 | Class: 4 | Level: 5 | Craftsmanship: 6 | Control: 7 | CP: 8 | Recipe Name: 9 | Recipe Level: 10 | Solver Seed: (Look at the top of the Execution Log on the Solver page) 11 | 12 | ## Expected Behaviour 13 | 14 | (Example: Standard Synthesis progress is 42) 15 | 16 | ## Actual Behaviour 17 | 18 | (Example: Standard Synthesis progress is 38) 19 | 20 | ## Steps To Reproduce 21 | 22 | (Example: Buffs, Food, Synthesis Condition, Simulator/Solver Options if different from default) 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | !.gitkeep 3 | node_modules/ 4 | tmp 5 | .DS_Store 6 | .idea/ 7 | *.iml 8 | Thumbs.db 9 | /app/.vs 10 | package-lock.json 11 | *.zip -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:4-onbuild 2 | EXPOSE 8001 3 | 4 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:4 2 | EXPOSE 8001 3 | VOLUME /usr/src/app 4 | WORKDIR /usr/src/app 5 | CMD [ "npm", "start" ] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 by Rhoda Baker and Gordon Tyler. 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source 20 | distribution. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FFXIV Crafting Optimizer Website 2 | 3 | This project contains the source for the [FFXIV Crafting Optimizer Website](http://ffxiv.lokyst.net/). It uses [AngularJS](http://angularjs.org/), [AngularUI Bootstrap](http://angular-ui.github.io/bootstrap/), and [Bootstrap](http://getbootstrap.com/). 4 | 5 | ### Running the app during development 6 | 7 | You can pick one of these options: 8 | 9 | * serve the `app` subdirectory in this repository with your webserver 10 | * install [node.js](https://nodejs.org/) and run: 11 | * `npm install` 12 | * `npm start` 13 | * install [browser-sync](https://www.browsersync.io/) and run: 14 | * `browser-sync start --port 8001 --server app --files app` 15 | * install Docker and run: 16 | * `docker build -f Dockerfile.dev -t ffxiv-craft-opt-web-dev .` 17 | * `docker run --rm -it -p 8001:8001 ffxiv-craft-opt-web-dev` 18 | 19 | The node.js, browser-sync and Docker methods options will serve the website on port 8001. Browser-sync should automatically launch your default browser and load the app. 20 | 21 | Note that if you're using Docker on Windows or OS X via VirtualBox, you'll have to use the IP address of the Linux VM (usually 192.168.99.100) that is hosting Docker, instead of `localhost`. The Dockerfile.dev method will mount the app source as a volume so changes will be reflected when the browser is refreshed. 22 | 23 | ### Translations 24 | 25 | Localization files can be found in `app/locale`. The `app/locale/en.json` file is purposefully missing because the English strings are used as the translation keys. Strings which require interpolation are defined in app.js so that they can be displayed immediately as a fallback until the actual locale json file finishes loading. 26 | -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | FileETag MTime Size 2 | Header set Cache-Control "no-cache, private, max-age=300" 3 | -------------------------------------------------------------------------------- /app/components/action-sequence.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
8 | 9 | {{actionForName(action).cpCost}} 10 | 11 | 12 | 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /app/components/action-table.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
13 | 14 | {{actionForName(action).cpCost}} 15 | 16 | 17 | 18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /app/components/buffs.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | {{buff.count}} 8 |
9 |
10 | -------------------------------------------------------------------------------- /app/components/macros.html: -------------------------------------------------------------------------------- 1 |

{{ 'MACRO_CTRL' | translate }}

2 |
3 |
4 |
5 | 8 |
9 |
10 |
11 |
12 |
Macro #{{$index+1}} ({{macro.time}}s)
13 | 14 |
15 | -------------------------------------------------------------------------------- /app/components/simulator-status.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Lv.{{stats.level}} {{recipe.cls | translate}} 5 |  ({{ 'Specialist' | translate }}) 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 |
{{ 'PROGRESS' | translate }} (100% {{ 'EFFICIENCY' | translate }} = {{baseValues.progress}})
14 | 15 | {{progress}} / {{recipe.difficulty}}
{{ 'QUALITY' | translate }}  (100% {{ 'EFFICIENCY' | translate }} = {{baseValues.quality}})
23 | 24 | {{quality}} / {{recipe.maxQuality}}
{{ 'CP' | translate }}
32 | 33 | {{cp}} / {{maxCp}}
37 |
38 | 39 | 40 | 43 | 46 | 49 | 50 |
41 | {{ 'DURABILITY' | translate }} {{durability}} / {{recipe.durability}}  42 | 44 | {{ 'HQ' | translate }} {{hqPercent}}%  45 | 47 | {{ 'RELIABILITY' | translate }} {{successPercent | number: 0}}% 48 |
51 |
52 | 53 |
54 |
55 | {{ 'ATTRIBUTES' | translate}} 56 | 57 |
58 |
59 |
60 | 61 | 62 | 69 | 76 | 77 |
63 | {{ 'FOOD' | translate }}
64 | 68 |
70 | {{ 'MEDICINE' | translate }}
71 | 75 |
78 | 79 | 80 | 81 | 82 | 83 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 122 | 123 | 124 | 125 |
{{ 'STATS_CRAFTSMANSHIP' | translate }}{{baseStats.craftsmanship}}+ {{buffStats.craftsmanship}} 84 | +  85 | 87 | = {{stats.craftsmanship}} 
{{ 'STATS_CONTROL' | translate }}{{baseStats.control}}+ {{buffStats.control}} 96 | +  97 | 98 | = {{stats.control}} 
{{ 'STATS_CP' | translate }}{{baseStats.cp}}+ {{buffStats.cp}} 107 | +  108 | 109 | = {{stats.cp}} 
{{ 'STATS_START_QUALITY' | translate }}0+ 0 118 | +  119 | 121 | = {{bonusStats.startQuality}} 
126 |
127 |
128 |
129 | -------------------------------------------------------------------------------- /app/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/css/.gitkeep -------------------------------------------------------------------------------- /app/css/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 60px; 3 | } 4 | 5 | .sidebar-nav { 6 | padding: 9px 0; 7 | } 8 | 9 | @media (max-width: 980px) { 10 | /* Enable use of floated navbar text */ 11 | .navbar-text.pull-right { 12 | float: none; 13 | padding-left: 5px; 14 | padding-right: 5px; 15 | } 16 | 17 | body { 18 | padding-top: 0px; 19 | } 20 | } 21 | 22 | .nav, .pagination, .carousel a { cursor: pointer; } 23 | 24 | footer { 25 | padding: 20px; 26 | background-color: #f5f5f5; 27 | } 28 | 29 | footer p { 30 | margin: 0px; 31 | } 32 | 33 | @media (max-width: 767px) { 34 | footer { 35 | margin-left: -20px; 36 | margin-right: -20px; 37 | padding-left: 20px; 38 | padding-right: 20px; 39 | } 40 | } 41 | 42 | /* Disable tooltips for iPad/iPhone */ 43 | @media (max-device-width: 768px) { 44 | .tooltip { 45 | visibility: hidden; 46 | } 47 | } -------------------------------------------------------------------------------- /app/css/instructions.css: -------------------------------------------------------------------------------- 1 | /* instructions css stylesheet */ 2 | 3 | section { 4 | padding-top: 10px; 5 | padding-bottom: 20px; 6 | } -------------------------------------------------------------------------------- /app/data/.htaccess: -------------------------------------------------------------------------------- 1 | Header set Cache-Control "public, max-age=1800, must-revalidate" 2 | -------------------------------------------------------------------------------- /app/data/buffs/Medicine.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "control_percent": 2, 4 | "control_value": 34, 5 | "hq": false, 6 | "id": "99c8f8f8de2", 7 | "name": { 8 | "cn": "巨匠药水", 9 | "de": "Artisanentrank der Fachkenntnis", 10 | "en": "Commanding Craftsman's Syrup", 11 | "fr": "Potion de maîtrise", 12 | "ja": "巨匠の水薬", 13 | "ko": "Commanding Craftsman's Syrup" 14 | } 15 | }, 16 | { 17 | "control_percent": 3, 18 | "control_value": 42, 19 | "hq": true, 20 | "id": "99c8f8f8de2", 21 | "name": { 22 | "cn": "巨匠药水", 23 | "de": "Artisanentrank der Fachkenntnis", 24 | "en": "Commanding Craftsman's Syrup", 25 | "fr": "Potion de maîtrise", 26 | "ja": "巨匠の水薬", 27 | "ko": "Commanding Craftsman's Syrup" 28 | } 29 | }, 30 | { 31 | "control_percent": 2, 32 | "control_value": 20, 33 | "hq": false, 34 | "id": "3574f8c53d8", 35 | "name": { 36 | "cn": "巨匠药茶", 37 | "de": "Handwerkerlegenden-Tee", 38 | "en": "Commanding Craftsman's Tea", 39 | "fr": "Infusion de maîtrise", 40 | "ja": "巨匠の薬茶", 41 | "ko": "거장의 약차" 42 | } 43 | }, 44 | { 45 | "control_percent": 3, 46 | "control_value": 25, 47 | "hq": true, 48 | "id": "3574f8c53d8", 49 | "name": { 50 | "cn": "巨匠药茶", 51 | "de": "Handwerkerlegenden-Tee", 52 | "en": "Commanding Craftsman's Tea", 53 | "fr": "Infusion de maîtrise", 54 | "ja": "巨匠の薬茶", 55 | "ko": "거장의 약차" 56 | } 57 | }, 58 | { 59 | "craftsmanship_percent": 2, 60 | "craftsmanship_value": 33, 61 | "hq": false, 62 | "id": "92d3ba5a6d6", 63 | "name": { 64 | "cn": "名匠药水", 65 | "de": "Artisanentrank der Effizienz", 66 | "en": "Competent Craftsman's Syrup", 67 | "fr": "Potion de virtuosité", 68 | "ja": "名匠の水薬", 69 | "ko": "Competent Craftsman's Syrup" 70 | } 71 | }, 72 | { 73 | "craftsmanship_percent": 3, 74 | "craftsmanship_value": 41, 75 | "hq": true, 76 | "id": "92d3ba5a6d6", 77 | "name": { 78 | "cn": "名匠药水", 79 | "de": "Artisanentrank der Effizienz", 80 | "en": "Competent Craftsman's Syrup", 81 | "fr": "Potion de virtuosité", 82 | "ja": "名匠の水薬", 83 | "ko": "Competent Craftsman's Syrup" 84 | } 85 | }, 86 | { 87 | "craftsmanship_percent": 2, 88 | "craftsmanship_value": 20, 89 | "hq": false, 90 | "id": "373c88c80d3", 91 | "name": { 92 | "cn": "名匠药茶", 93 | "de": "Handwerkermeister-Tee", 94 | "en": "Competent Craftsman's Tea", 95 | "fr": "Infusion de virtuosité", 96 | "ja": "名匠の薬茶", 97 | "ko": "장인의 약차" 98 | } 99 | }, 100 | { 101 | "craftsmanship_percent": 3, 102 | "craftsmanship_value": 25, 103 | "hq": true, 104 | "id": "373c88c80d3", 105 | "name": { 106 | "cn": "名匠药茶", 107 | "de": "Handwerkermeister-Tee", 108 | "en": "Competent Craftsman's Tea", 109 | "fr": "Infusion de virtuosité", 110 | "ja": "名匠の薬茶", 111 | "ko": "장인의 약차" 112 | } 113 | }, 114 | { 115 | "cp_percent": 5, 116 | "cp_value": 13, 117 | "hq": false, 118 | "id": "4fe710c87f9", 119 | "name": { 120 | "cn": "魔匠药水", 121 | "de": "Artisanentrank des Fachwissens", 122 | "en": "Cunning Craftsman's Syrup", 123 | "fr": "Potion de capacité", 124 | "ja": "魔匠の水薬", 125 | "ko": "Cunning Craftsman's Syrup" 126 | } 127 | }, 128 | { 129 | "cp_percent": 6, 130 | "cp_value": 16, 131 | "hq": true, 132 | "id": "4fe710c87f9", 133 | "name": { 134 | "cn": "魔匠药水", 135 | "de": "Artisanentrank des Fachwissens", 136 | "en": "Cunning Craftsman's Syrup", 137 | "fr": "Potion de capacité", 138 | "ja": "魔匠の水薬", 139 | "ko": "Cunning Craftsman's Syrup" 140 | } 141 | }, 142 | { 143 | "cp_percent": 4, 144 | "cp_value": 10, 145 | "hq": false, 146 | "id": "e01fa653061", 147 | "name": { 148 | "cn": "魔匠药茶", 149 | "de": "Handwerkerkoryphäen-Tee", 150 | "en": "Cunning Craftsman's Tea", 151 | "fr": "Infusion de capacité", 152 | "ja": "魔匠の薬茶", 153 | "ko": "명인의 약차" 154 | } 155 | }, 156 | { 157 | "cp_percent": 5, 158 | "cp_value": 13, 159 | "hq": true, 160 | "id": "e01fa653061", 161 | "name": { 162 | "cn": "魔匠药茶", 163 | "de": "Handwerkerkoryphäen-Tee", 164 | "en": "Cunning Craftsman's Tea", 165 | "fr": "Infusion de capacité", 166 | "ja": "魔匠の薬茶", 167 | "ko": "명인의 약차" 168 | } 169 | } 170 | ] -------------------------------------------------------------------------------- /app/data/recipedb/normalize.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | for n in ["Alchemist","Armorer","Blacksmith","Carpenter","Culinarian","Goldsmith","Leatherworker","Weaver"]: 4 | with open(f"{n}.json", mode="rt", encoding="utf-8") as f: 5 | recipes = json.load(f) 6 | with open(f"{n}.json", mode="wt", encoding="utf-8") as f: 7 | json.dump(recipes, f, indent=2, sort_keys=True, ensure_ascii=False) 8 | -------------------------------------------------------------------------------- /app/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/.gitkeep -------------------------------------------------------------------------------- /app/img/.htaccess: -------------------------------------------------------------------------------- 1 | Header set Cache-Control "public, max-age=1800, must-revalidate" 2 | -------------------------------------------------------------------------------- /app/img/actions/Alchemist/advancedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/advancedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/basicSynth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/basicSynth.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/basicSynth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/basicSynth2.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/basicTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/basicTouch.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/comfortZone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/comfortZone.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/delicateSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/delicateSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/focusedSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/focusedSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/focusedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/focusedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/groundwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/groundwork.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/intensiveSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/intensiveSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/patientTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/patientTouch.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/preciseTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/preciseTouch.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/preparatoryTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/preparatoryTouch.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/prudentTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/prudentTouch.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/standardSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/standardSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Alchemist/standardTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Alchemist/standardTouch.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/advancedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/advancedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/basicSynth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/basicSynth.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/basicSynth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/basicSynth2.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/basicTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/basicTouch.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/delicateSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/delicateSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/focusedSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/focusedSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/focusedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/focusedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/groundwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/groundwork.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/intensiveSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/intensiveSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/patientTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/patientTouch.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/pieceByPiece.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/pieceByPiece.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/preciseTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/preciseTouch.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/preparatoryTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/preparatoryTouch.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/prudentTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/prudentTouch.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/standardSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/standardSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Armorer/standardTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Armorer/standardTouch.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/advancedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/advancedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/basicSynth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/basicSynth.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/basicSynth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/basicSynth2.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/basicTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/basicTouch.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/delicateSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/delicateSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/focusedSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/focusedSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/focusedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/focusedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/groundwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/groundwork.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/intensiveSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/intensiveSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/patientTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/patientTouch.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/preciseTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/preciseTouch.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/preparatoryTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/preparatoryTouch.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/prudentTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/prudentTouch.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/standardSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/standardSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Blacksmith/standardTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Blacksmith/standardTouch.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/advancedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/advancedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/basicSynth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/basicSynth.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/basicSynth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/basicSynth2.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/basicTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/basicTouch.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/delicateSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/delicateSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/focusedSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/focusedSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/focusedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/focusedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/groundwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/groundwork.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/intensiveSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/intensiveSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/patientTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/patientTouch.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/preciseTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/preciseTouch.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/preparatoryTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/preparatoryTouch.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/prudentTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/prudentTouch.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/standardSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/standardSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Carpenter/standardTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Carpenter/standardTouch.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/advancedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/advancedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/basicSynth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/basicSynth.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/basicSynth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/basicSynth2.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/basicTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/basicTouch.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/delicateSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/delicateSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/focusedSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/focusedSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/focusedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/focusedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/groundwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/groundwork.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/intensiveSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/intensiveSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/patientTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/patientTouch.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/preciseTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/preciseTouch.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/preparatoryTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/preparatoryTouch.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/prudentTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/prudentTouch.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/standardSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/standardSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Culinarian/standardTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Culinarian/standardTouch.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/advancedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/advancedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/basicSynth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/basicSynth.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/basicSynth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/basicSynth2.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/basicTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/basicTouch.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/delicateSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/delicateSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/focusedSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/focusedSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/focusedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/focusedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/groundwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/groundwork.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/intensiveSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/intensiveSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/patientTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/patientTouch.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/preciseTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/preciseTouch.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/preparatoryTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/preparatoryTouch.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/prudentTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/prudentTouch.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/standardSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/standardSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Goldsmith/standardTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Goldsmith/standardTouch.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/advancedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/advancedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/basicSynth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/basicSynth.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/basicSynth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/basicSynth2.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/basicTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/basicTouch.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/delicateSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/delicateSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/focusedSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/focusedSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/focusedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/focusedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/groundwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/groundwork.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/intensiveSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/intensiveSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/patientTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/patientTouch.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/preciseTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/preciseTouch.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/preparatoryTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/preparatoryTouch.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/prudentTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/prudentTouch.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/standardSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/standardSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Leatherworker/standardTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Leatherworker/standardTouch.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/advancedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/advancedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/basicSynth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/basicSynth.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/basicSynth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/basicSynth2.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/basicTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/basicTouch.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/carefulSynthesis2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/carefulSynthesis2.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/delicateSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/delicateSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/focusedSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/focusedSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/focusedTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/focusedTouch.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/groundwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/groundwork.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/intensiveSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/intensiveSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/patientTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/patientTouch.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/preciseTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/preciseTouch.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/preparatoryTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/preparatoryTouch.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/prudentTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/prudentTouch.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/standardSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/standardSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/Weaver/standardTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/Weaver/standardTouch.png -------------------------------------------------------------------------------- /app/img/actions/brandOfTheElements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/brandOfTheElements.png -------------------------------------------------------------------------------- /app/img/actions/byregotsBlessing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/byregotsBlessing.png -------------------------------------------------------------------------------- /app/img/actions/byregotsMiracle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/byregotsMiracle.png -------------------------------------------------------------------------------- /app/img/actions/carefulSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/carefulSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/carefulSynthesis3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/carefulSynthesis3.png -------------------------------------------------------------------------------- /app/img/actions/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/empty.png -------------------------------------------------------------------------------- /app/img/actions/flawlessSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/flawlessSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/greatStrides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/greatStrides.png -------------------------------------------------------------------------------- /app/img/actions/hastyTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/hastyTouch.png -------------------------------------------------------------------------------- /app/img/actions/hastyTouch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/hastyTouch2.png -------------------------------------------------------------------------------- /app/img/actions/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/heart.png -------------------------------------------------------------------------------- /app/img/actions/initialPreparations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/initialPreparations.png -------------------------------------------------------------------------------- /app/img/actions/innerQuiet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/innerQuiet.png -------------------------------------------------------------------------------- /app/img/actions/innovation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/innovation.png -------------------------------------------------------------------------------- /app/img/actions/innovativeTouch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/innovativeTouch.png -------------------------------------------------------------------------------- /app/img/actions/makersMark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/makersMark.png -------------------------------------------------------------------------------- /app/img/actions/manipulation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/manipulation.png -------------------------------------------------------------------------------- /app/img/actions/manipulation2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/manipulation2.png -------------------------------------------------------------------------------- /app/img/actions/mastersMend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/mastersMend.png -------------------------------------------------------------------------------- /app/img/actions/mastersMend2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/mastersMend2.png -------------------------------------------------------------------------------- /app/img/actions/muscleMemory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/muscleMemory.png -------------------------------------------------------------------------------- /app/img/actions/nameOfTheElements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/nameOfTheElements.png -------------------------------------------------------------------------------- /app/img/actions/nymeiasWheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/nymeiasWheel.png -------------------------------------------------------------------------------- /app/img/actions/observe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/observe.png -------------------------------------------------------------------------------- /app/img/actions/rapidSynthesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/rapidSynthesis.png -------------------------------------------------------------------------------- /app/img/actions/rapidSynthesis2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/rapidSynthesis2.png -------------------------------------------------------------------------------- /app/img/actions/rapidSynthesis3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/rapidSynthesis3.png -------------------------------------------------------------------------------- /app/img/actions/reclaim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/reclaim.png -------------------------------------------------------------------------------- /app/img/actions/reflect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/reflect.png -------------------------------------------------------------------------------- /app/img/actions/satisfaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/satisfaction.png -------------------------------------------------------------------------------- /app/img/actions/specialtyRefurbish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/specialtyRefurbish.png -------------------------------------------------------------------------------- /app/img/actions/specialtyReinforce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/specialtyReinforce.png -------------------------------------------------------------------------------- /app/img/actions/steadyHand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/steadyHand.png -------------------------------------------------------------------------------- /app/img/actions/steadyHand2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/steadyHand2.png -------------------------------------------------------------------------------- /app/img/actions/strokeOfGenius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/strokeOfGenius.png -------------------------------------------------------------------------------- /app/img/actions/trainedEye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/trainedEye.png -------------------------------------------------------------------------------- /app/img/actions/trainedHand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/trainedHand.png -------------------------------------------------------------------------------- /app/img/actions/trainedInstinct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/trainedInstinct.png -------------------------------------------------------------------------------- /app/img/actions/tricksOfTheTrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/tricksOfTheTrade.png -------------------------------------------------------------------------------- /app/img/actions/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/unknown.png -------------------------------------------------------------------------------- /app/img/actions/unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | image/svg+xml 10 | 11 | 12 | 13 | 14 | Openclipart 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/img/actions/veneration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/veneration.png -------------------------------------------------------------------------------- /app/img/actions/wasteNot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/wasteNot.png -------------------------------------------------------------------------------- /app/img/actions/wasteNot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/wasteNot2.png -------------------------------------------------------------------------------- /app/img/actions/whistle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/actions/whistle.png -------------------------------------------------------------------------------- /app/img/classes/Alchemist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/classes/Alchemist.png -------------------------------------------------------------------------------- /app/img/classes/Armorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/classes/Armorer.png -------------------------------------------------------------------------------- /app/img/classes/Blacksmith.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/classes/Blacksmith.png -------------------------------------------------------------------------------- /app/img/classes/Carpenter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/classes/Carpenter.png -------------------------------------------------------------------------------- /app/img/classes/Culinarian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/classes/Culinarian.png -------------------------------------------------------------------------------- /app/img/classes/Goldsmith.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/classes/Goldsmith.png -------------------------------------------------------------------------------- /app/img/classes/Leatherworker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/classes/Leatherworker.png -------------------------------------------------------------------------------- /app/img/classes/Weaver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/classes/Weaver.png -------------------------------------------------------------------------------- /app/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /app/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /app/img/instructions/AvailableActions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/AvailableActions.png -------------------------------------------------------------------------------- /app/img/instructions/AvailableActions_cn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/AvailableActions_cn.jpg -------------------------------------------------------------------------------- /app/img/instructions/AvailableActions_de.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/AvailableActions_de.jpg -------------------------------------------------------------------------------- /app/img/instructions/AvailableActions_en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/AvailableActions_en.jpg -------------------------------------------------------------------------------- /app/img/instructions/AvailableActions_fr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/AvailableActions_fr.jpg -------------------------------------------------------------------------------- /app/img/instructions/AvailableActions_ja.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/AvailableActions_ja.jpg -------------------------------------------------------------------------------- /app/img/instructions/AvailableActions_ko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/AvailableActions_ko.jpg -------------------------------------------------------------------------------- /app/img/instructions/CrafterDetails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/CrafterDetails.png -------------------------------------------------------------------------------- /app/img/instructions/CrafterDetails_cn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/CrafterDetails_cn.jpg -------------------------------------------------------------------------------- /app/img/instructions/CrafterDetails_de.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/CrafterDetails_de.jpg -------------------------------------------------------------------------------- /app/img/instructions/CrafterDetails_en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/CrafterDetails_en.jpg -------------------------------------------------------------------------------- /app/img/instructions/CrafterDetails_fr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/CrafterDetails_fr.jpg -------------------------------------------------------------------------------- /app/img/instructions/CrafterDetails_ja.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/CrafterDetails_ja.jpg -------------------------------------------------------------------------------- /app/img/instructions/CrafterDetails_ko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/CrafterDetails_ko.jpg -------------------------------------------------------------------------------- /app/img/instructions/SequenceEditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SequenceEditor.png -------------------------------------------------------------------------------- /app/img/instructions/SequenceEditor_cn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SequenceEditor_cn.jpg -------------------------------------------------------------------------------- /app/img/instructions/SequenceEditor_de.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SequenceEditor_de.jpg -------------------------------------------------------------------------------- /app/img/instructions/SequenceEditor_en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SequenceEditor_en.jpg -------------------------------------------------------------------------------- /app/img/instructions/SequenceEditor_fr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SequenceEditor_fr.jpg -------------------------------------------------------------------------------- /app/img/instructions/SequenceEditor_ja.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SequenceEditor_ja.jpg -------------------------------------------------------------------------------- /app/img/instructions/SequenceEditor_ko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SequenceEditor_ko.jpg -------------------------------------------------------------------------------- /app/img/instructions/SimulationLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SimulationLog.png -------------------------------------------------------------------------------- /app/img/instructions/SimulationLog_cn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SimulationLog_cn.jpg -------------------------------------------------------------------------------- /app/img/instructions/SimulationLog_de.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SimulationLog_de.jpg -------------------------------------------------------------------------------- /app/img/instructions/SimulationLog_en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SimulationLog_en.jpg -------------------------------------------------------------------------------- /app/img/instructions/SimulationLog_fr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SimulationLog_fr.jpg -------------------------------------------------------------------------------- /app/img/instructions/SimulationLog_ja.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SimulationLog_ja.jpg -------------------------------------------------------------------------------- /app/img/instructions/SimulationLog_ko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SimulationLog_ko.jpg -------------------------------------------------------------------------------- /app/img/instructions/Simulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/Simulator.png -------------------------------------------------------------------------------- /app/img/instructions/Simulator_cn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/Simulator_cn.jpg -------------------------------------------------------------------------------- /app/img/instructions/Simulator_de.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/Simulator_de.jpg -------------------------------------------------------------------------------- /app/img/instructions/Simulator_en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/Simulator_en.jpg -------------------------------------------------------------------------------- /app/img/instructions/Simulator_fr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/Simulator_fr.jpg -------------------------------------------------------------------------------- /app/img/instructions/Simulator_ja.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/Simulator_ja.jpg -------------------------------------------------------------------------------- /app/img/instructions/Simulator_ko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/Simulator_ko.jpg -------------------------------------------------------------------------------- /app/img/instructions/StatBonuses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/StatBonuses.png -------------------------------------------------------------------------------- /app/img/instructions/SynthDetails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SynthDetails.png -------------------------------------------------------------------------------- /app/img/instructions/SynthDetails_cn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SynthDetails_cn.jpg -------------------------------------------------------------------------------- /app/img/instructions/SynthDetails_de.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SynthDetails_de.jpg -------------------------------------------------------------------------------- /app/img/instructions/SynthDetails_en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SynthDetails_en.jpg -------------------------------------------------------------------------------- /app/img/instructions/SynthDetails_fr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SynthDetails_fr.jpg -------------------------------------------------------------------------------- /app/img/instructions/SynthDetails_ja.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SynthDetails_ja.jpg -------------------------------------------------------------------------------- /app/img/instructions/SynthDetails_ko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongspark/ffxiv-craft-opt-web/3d3daf8f402d4a06dd580b0bf8035eddd405890f/app/img/instructions/SynthDetails_ko.jpg -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | FFXIV Crafting Optimizer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 63 | 64 |
65 |
66 | 69 | 70 |
71 |
72 |
73 |
74 | 75 | 81 |
82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /app/js/actions.js: -------------------------------------------------------------------------------- 1 | function Action(shortName, name, durabilityCost, cpCost, successProbability, qualityIncreaseMultiplier, progressIncreaseMultiplier, aType, activeTurns, cls, level, onGood, onExcellent, onPoor) { 2 | this.shortName = shortName; 3 | this.name = name; 4 | this.durabilityCost = durabilityCost; 5 | this.cpCost = cpCost; 6 | this.successProbability = successProbability; 7 | this.qualityIncreaseMultiplier = qualityIncreaseMultiplier; 8 | this.progressIncreaseMultiplier = progressIncreaseMultiplier; 9 | this.type = aType; 10 | 11 | if (aType != 'immediate') { 12 | this.activeTurns = activeTurns; // Save some space 13 | } 14 | else { 15 | this.activeTurns = 1; 16 | } 17 | 18 | this.cls = cls; 19 | this.level = level; 20 | this.onGood = onGood; 21 | this.onExcellent = onExcellent; 22 | this.onPoor = onPoor; 23 | } 24 | 25 | // Actions Table 26 | //============== 27 | //parameters: shortName, name, durabilityCost, cpCost, successProbability, qualityIncreaseMultiplier, progressIncreaseMultiplier, aType, activeTurns, cls, level,onGood, onExcl, onPoor 28 | var AllActions = { 29 | // shortName, fullName, dur, cp, Prob, QIM, PIM, Type, t, cls, lvl, onGood, onExcl, onPoor 30 | observe: new Action( 'observe', 'Observe', 0, 7, 1.0, 0.0, 0.0, 'immediate', 1, 'All', 13), 31 | 32 | basicSynth: new Action( 'basicSynth', 'Basic Synthesis', 10, 0, 1.0, 0.0, 1.0, 'immediate', 1, 'All', 1), 33 | basicSynth2: new Action( 'basicSynth2', 'Basic Synthesis', 10, 0, 1.0, 0.0, 1.2, 'immediate', 1, 'All', 31), 34 | carefulSynthesis: new Action( 'carefulSynthesis', 'Careful Synthesis', 10, 7, 1.0, 0.0, 1.5, 'immediate', 1, 'All', 62), 35 | rapidSynthesis: new Action( 'rapidSynthesis', 'Rapid Synthesis', 10, 0, 0.5, 0.0, 2.5, 'immediate', 1, 'All', 9), 36 | flawlessSynthesis: new Action( 'flawlessSynthesis', 'Flawless Synthesis', 10, 15, 0.9, 0.0, 1.0, 'immediate', 1, 'All', 37), 37 | 38 | basicTouch: new Action( 'basicTouch', 'Basic Touch', 10, 18, 1.0, 1.0, 0.0, 'immediate', 1, 'All', 5), 39 | standardTouch: new Action( 'standardTouch', 'Standard Touch', 10, 32, 1.0, 1.25,0.0, 'immediate', 1, 'All', 18), 40 | hastyTouch: new Action( 'hastyTouch', 'Hasty Touch', 10, 0, 0.6, 1.0, 0.0, 'immediate', 1, 'All', 9), 41 | byregotsBlessing: new Action( 'byregotsBlessing', 'Byregot\'s Blessing', 10, 24, 1.0, 1.0, 0.0, 'immediate', 1, 'All', 50), 42 | 43 | mastersMend: new Action( 'mastersMend', 'Master\'s Mend', 0, 88, 1.0, 0.0, 0.0, 'immediate', 1, 'All', 7), 44 | tricksOfTheTrade: new Action( 'tricksOfTheTrade', 'Tricks of the Trade', 0, 0, 1.0, 0.0, 0.0, 'immediate', 1, 'All', 13, true, true), 45 | 46 | innerQuiet: new Action( 'innerQuiet', 'Inner Quiet', 0, 18, 1.0, 0.0, 0.0, 'countup', 1, 'All', 11), 47 | manipulation: new Action( 'manipulation', 'Manipulation', 0, 96, 1.0, 0.0, 0.0, 'countdown', 8, 'All', 65), 48 | wasteNot: new Action( 'wasteNot', 'Waste Not', 0, 56, 1.0, 0.0, 0.0, 'countdown', 4, 'All', 15), 49 | wasteNot2: new Action( 'wasteNot2', 'Waste Not II', 0, 98, 1.0, 0.0, 0.0, 'countdown', 8, 'All', 47), 50 | veneration: new Action( 'veneration', 'Veneration', 0, 18, 1.0, 0.0, 0.0, 'countdown', 4, 'All', 15), 51 | innovation: new Action( 'innovation', 'Innovation', 0, 18, 1.0, 0.0, 0.0, 'countdown', 4, 'All', 26), 52 | greatStrides: new Action( 'greatStrides', 'Great Strides', 0, 32, 1.0, 0.0, 0.0, 'countdown', 3, 'All', 21), 53 | 54 | // Heavensward actions 55 | preciseTouch: new Action( 'preciseTouch', 'Precise Touch', 10, 18, 1.0, 1.5, 0.0, 'immediate', 1, 'All', 53, true, true), 56 | muscleMemory: new Action( 'muscleMemory', 'Muscle Memory', 10, 6, 1.0, 0.0, 3.0, 'countdown', 5, 'All', 54), 57 | 58 | // Elemental Actions 59 | brandOfTheElements: new Action( 'brandOfTheElements', 'Brand of the Elements', 10, 6, 1.0, 0.0, 1.0, 'immediate', 1, 'All', 37), 60 | nameOfTheElements: new Action( 'nameOfTheElements', 'Name of the Elements', 0, 30, 1.0, 0.0, 0.0, 'countdown', 3, 'All', 37), 61 | 62 | // Stormblood actions 63 | rapidSynthesis2: new Action( 'rapidSynthesis2', 'Rapid Synthesis', 10, 0, 0.5, 0.0, 5.0, 'immediate', 1, 'All', 63), 64 | patientTouch: new Action( 'patientTouch', 'Patient Touch', 10, 6, 0.5, 1.0, 0.0, 'immediate', 1, 'All', 64), 65 | prudentTouch: new Action( 'prudentTouch', 'Prudent Touch', 5, 25, 1.0, 1.0, 0.0, 'immediate', 1, 'All', 66), 66 | focusedSynthesis: new Action( 'focusedSynthesis', 'Focused Synthesis', 10, 5, 0.5, 0.0, 2.0, 'immediate', 1, 'All', 67), 67 | focusedTouch: new Action( 'focusedTouch', 'Focused Touch', 10, 18, 0.5, 1.5, 0.0, 'immediate', 1, 'All', 68), 68 | reflect: new Action( 'reflect', 'Reflect', 10, 24, 1.0, 1.0, 0.0, 'immediate', 1, 'All', 69), 69 | 70 | // ShadowBringers actions 71 | preparatoryTouch: new Action( 'preparatoryTouch', 'Preparatory Touch', 20, 40, 1.0, 2.0, 0.0, 'immediate', 1, 'All', 71), 72 | groundwork: new Action( 'groundwork', 'Groundwork', 20, 18, 1.0, 0.0, 3.0, 'immediate', 1, 'All', 72), 73 | delicateSynthesis: new Action( 'delicateSynthesis', 'Delicate Synthesis', 10, 32, 1.0, 1.0, 1.0, 'immediate', 1, 'All', 76), 74 | intensiveSynthesis: new Action( 'intensiveSynthesis', 'Intensive Synthesis', 10, 6, 1.0, 0.0, 4.0, 'immediate', 1, 'All', 78, true, true), 75 | trainedEye: new Action( 'trainedEye', 'Trained Eye', 10, 250, 1.0, 0.0, 0.0, 'immediate', 1, 'All', 80), 76 | 77 | // Special Actions - not selectable 78 | dummyAction: new Action( 'dummyAction', '______________', 0, 0, 1.0, 0.0, 0.0, 'immediate', 1, 'All', 1) 79 | }; 80 | -------------------------------------------------------------------------------- /app/js/app.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb', [ 6 | 'ui.bootstrap', 7 | 'ui.router', 8 | 'pascalprecht.translate', 9 | 'lvl.directives.dragdrop', 10 | 'cgBusy', 11 | 'ffxivCraftOptWeb.services.locale', 12 | 'ffxivCraftOptWeb.services.actions', 13 | 'ffxivCraftOptWeb.services.bonusStats', 14 | 'ffxivCraftOptWeb.services.buffsdb', 15 | 'ffxivCraftOptWeb.services.storage', 16 | 'ffxivCraftOptWeb.services.profile', 17 | 'ffxivCraftOptWeb.services.recipelibrary', 18 | 'ffxivCraftOptWeb.services.simulator', 19 | 'ffxivCraftOptWeb.services.solver', 20 | 'ffxivCraftOptWeb.services.translateLocalStorage', 21 | 'ffxivCraftOptWeb.services.tooltips', 22 | 'ffxivCraftOptWeb.directives', 23 | 'ffxivCraftOptWeb.controllers', 24 | 'ffxivCraftOptWeb.components' 25 | ], config); 26 | 27 | function config($translateProvider) { 28 | $translateProvider.useStaticFilesLoader({ 29 | prefix: 'locale/', 30 | suffix: '.json' 31 | }); 32 | 33 | $translateProvider.useLoaderCache(true); 34 | 35 | // Backwards compatibility with v13. 36 | if (localStorage['lang']) { 37 | $translateProvider.preferredLanguage(localStorage['lang']); 38 | localStorage.removeItem('lang'); 39 | } 40 | else { 41 | $translateProvider.preferredLanguage('en'); 42 | } 43 | 44 | $translateProvider.useStorage('_translateLocalStorage'); 45 | } 46 | })(); 47 | -------------------------------------------------------------------------------- /app/js/components/action-sequence.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.components') 6 | .directive('actionSequence', factory); 7 | 8 | function factory() { 9 | return { 10 | restrict: 'E', 11 | templateUrl: 'components/action-sequence.html', 12 | scope: { 13 | class: '@', 14 | actions: '=', 15 | cls: '=', 16 | onClick: '=', 17 | actionClasses: '=', 18 | draggable: '=', 19 | tooltipPlacement: '@' 20 | }, 21 | controller: controller 22 | } 23 | } 24 | 25 | function controller($scope, _actionsByName, _tooltips, _getActionImagePath, _iActionClassSpecific) { 26 | $scope.getActionImagePath = _getActionImagePath; 27 | $scope.cssClassesForAction = cssClassesForAction; 28 | $scope.actionForName = actionForName; 29 | $scope.iActionClassSpecific = _iActionClassSpecific; 30 | 31 | $scope.actionTooltips = {}; 32 | 33 | $scope.$on("tooltipCacheUpdated", updateActionTooltips); 34 | $scope.$watch("cls", updateActionTooltips); 35 | 36 | updateActionTooltips(); 37 | 38 | ////////////////////////////////////////////////////////////////////////// 39 | 40 | function updateActionTooltips() { 41 | var newTooltips = {}; 42 | angular.forEach(_actionsByName, function (actionInfo) { 43 | var key; 44 | if (actionInfo.cls != 'All') { 45 | key = actionInfo.cls + actionInfo.shortName; 46 | } 47 | else { 48 | key = $scope.cls + actionInfo.shortName; 49 | } 50 | newTooltips[actionInfo.shortName] = _tooltips.actionTooltips[key]; 51 | }); 52 | $scope.actionTooltips = newTooltips; 53 | } 54 | 55 | function cssClassesForAction(action, index) { 56 | var classes = {}; 57 | if ($scope.actionClasses) { 58 | classes = $scope.actionClasses(action, $scope.cls, index); 59 | } 60 | return classes; 61 | } 62 | 63 | function actionForName(name) { 64 | return _actionsByName[name]; 65 | } 66 | } 67 | })(); 68 | -------------------------------------------------------------------------------- /app/js/components/action-table.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.components') 6 | .directive('actionTable', factory); 7 | 8 | function factory() { 9 | return { 10 | restrict: 'E', 11 | templateUrl: 'components/action-table.html', 12 | scope: { 13 | cls: '=', 14 | onClick: '=', 15 | actionClasses: '=', 16 | selectable: '=', 17 | draggable: '=', 18 | tooltipPlacement: '@' 19 | }, 20 | controller: controller 21 | } 22 | } 23 | 24 | function controller($scope, _actionGroups, _actionsByName, _tooltips, _getActionImagePath, _iActionClassSpecific) { 25 | $scope.actionGroups = _actionGroups; 26 | $scope.getActionImagePath = _getActionImagePath; 27 | $scope.cssClassesForAction = cssClassesForAction; 28 | $scope.actionForName = actionForName; 29 | $scope.iActionClassSpecific = _iActionClassSpecific; 30 | 31 | $scope.actionTooltips = {}; 32 | 33 | $scope.$on("tooltipCacheUpdated", updateActionTooltips); 34 | $scope.$watch("cls", updateActionTooltips); 35 | 36 | updateActionTooltips(); 37 | 38 | ////////////////////////////////////////////////////////////////////////// 39 | 40 | function updateActionTooltips() { 41 | var newTooltips = {}; 42 | angular.forEach(_actionsByName, function (action) { 43 | var key; 44 | if (action.cls != 'All') { 45 | key = action.cls + action.shortName; 46 | } 47 | else { 48 | key = $scope.cls + action.shortName; 49 | } 50 | newTooltips[action.shortName] = _tooltips.actionTooltips[key]; 51 | }); 52 | $scope.actionTooltips = newTooltips; 53 | } 54 | 55 | function cssClassesForAction(name) { 56 | var classes = $scope.actionClasses(name, $scope.cls); 57 | classes['selectable'] = $scope.selectable; 58 | return classes; 59 | } 60 | 61 | function actionForName(name) { 62 | return _actionsByName[name]; 63 | } 64 | 65 | } 66 | })(); 67 | -------------------------------------------------------------------------------- /app/js/components/buffs.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.components') 6 | .directive('buffs', factory); 7 | 8 | function factory() { 9 | return { 10 | restrict: 'E', 11 | templateUrl: 'components/buffs.html', 12 | scope: { 13 | effects: '=', 14 | recipe: '=' 15 | }, 16 | controller: controller 17 | }; 18 | } 19 | 20 | function controller($scope, _getActionImagePath) { 21 | $scope.getActionImagePath = _getActionImagePath; 22 | 23 | $scope.$watchCollection('effects', update); 24 | 25 | update(); 26 | 27 | ////////////////////////////////////////////////////////////////////////// 28 | 29 | function update() { 30 | $scope.buffs = []; 31 | 32 | var effects = $scope.effects; 33 | if (effects) { 34 | for (var name in effects.indefinites) { 35 | $scope.buffs.push({name: name, count: ''}); 36 | } 37 | for (var name in effects.countUps) { 38 | $scope.buffs.push({name: name, count: effects.countUps[name] + 1}); 39 | } 40 | for (var name in effects.countDowns) { 41 | $scope.buffs.push({name: name, count: effects.countDowns[name]}); 42 | } 43 | } 44 | } 45 | } 46 | })(); 47 | -------------------------------------------------------------------------------- /app/js/components/dropzone.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.directives') 6 | .directive('myDropzone', dropzone); 7 | 8 | function dropzone() { 9 | return { 10 | restrict: 'A', 11 | link: function (scope, elem, attrs) { 12 | elem.bind('dragover', function (e) { 13 | e.preventDefault(); 14 | }); 15 | elem.bind('dragenter', function (e) { 16 | e.preventDefault(); 17 | }); 18 | elem.bind('drop', function (e) { 19 | e.stopPropagation(); 20 | e.preventDefault(); 21 | 22 | var files = e.dataTransfer.files; 23 | for (var i = 0, f; f = files[i]; i++) { 24 | var reader = new FileReader(); 25 | reader.readAsArrayBuffer(f); 26 | 27 | reader.onload = (function (f, reader) { 28 | return function () { 29 | var data = { 30 | name: f.name, 31 | type: f.type, 32 | size: f.size, 33 | lastModifiedDate: f.lastModifiedDate, 34 | content: reader.result 35 | }; 36 | 37 | var handler = scope.$eval(attrs['dropHandler']); 38 | scope.$apply(function () { handler(data); }); 39 | }; 40 | })(f, reader); 41 | } 42 | }); 43 | } 44 | } 45 | } 46 | })(); 47 | -------------------------------------------------------------------------------- /app/js/components/macros.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var MAX_LINES = 15; 5 | 6 | angular 7 | .module('ffxivCraftOptWeb.components') 8 | .directive('macros', factory); 9 | 10 | function factory() { 11 | return { 12 | restrict: 'E', 13 | templateUrl: 'components/macros.html', 14 | scope: { 15 | sequence: '=', 16 | cls: '=', 17 | options: '=' 18 | }, 19 | controller: controller 20 | } 21 | } 22 | 23 | function controller($scope, $translate, _actionsByName, _allActions, _iActionClassSpecific) { 24 | $scope.macroList = []; 25 | 26 | $scope.$on('$translateChangeSuccess', update); 27 | $scope.$watchCollection('sequence', update); 28 | $scope.$watch('cls', update); 29 | $scope.$watchCollection('options', update); 30 | 31 | update(); 32 | 33 | ////////////////////////////////////////////////////////////////////////// 34 | 35 | function update() { 36 | if (!angular.isDefined($scope.sequence)) { 37 | return; 38 | } 39 | 40 | var sequenceLines = buildSequenceLines($scope.options, $scope.sequence, extractBuffs()); 41 | $scope.macroList = buildMacroList($scope.options, sequenceLines); 42 | } 43 | 44 | function extractBuffs() { 45 | var buffs = {}; 46 | for (var i = 0; i < _allActions.length; i++) { 47 | var action = _allActions[i]; 48 | if (action.buff) { 49 | buffs[action.shortName] = true; 50 | } 51 | } 52 | return buffs; 53 | } 54 | 55 | /** 56 | * Function used to display sound effect on macro 57 | * 58 | * @param num number of sound to use 59 | * @param sound {boolean} true if sound is enabled, false else 60 | * @returns {string} 61 | */ 62 | function soundEffect(num, sound) { 63 | return sound ? '' : ''; 64 | } 65 | 66 | function buildSequenceLines(options, sequence, buffs) { 67 | var waitString = ''; 68 | var buffWaitString = ''; 69 | 70 | var lines = []; 71 | 72 | for (var i = 0; i < sequence.length; i++) { 73 | var action = sequence[i]; 74 | var info = _actionsByName[action]; 75 | if (info) { 76 | var actionName = $translate.instant(info.name); 77 | var line = '/ac "' + actionName + '" '; 78 | var time; 79 | if (buffs[action]) { 80 | line += buffWaitString; 81 | time = options.buffWaitTime; 82 | } 83 | else { 84 | line += waitString; 85 | time = options.waitTime 86 | } 87 | lines.push({text: line, time: time}); 88 | } 89 | else { 90 | lines.push({text: '/echo Error: Unknown action ' + action, time: 0}); 91 | } 92 | } 93 | 94 | return lines; 95 | } 96 | 97 | function buildMacroList(options, lines) { 98 | var macroList = []; 99 | 100 | var macroString = ''; 101 | var macroLineCount = 0; 102 | var macroTime = 0; 103 | var macroIndex = 1; 104 | 105 | if (options.includeMacroLock) { 106 | macroString += '/macrolock\n'; 107 | macroLineCount++; 108 | } 109 | 110 | for (var j = 0; j < lines.length; j++) { 111 | var line = lines[j]; 112 | macroString += line.text + '\n'; 113 | macroTime += line.time; 114 | macroLineCount += 1; 115 | 116 | if (macroLineCount === MAX_LINES - 1) { 117 | if (lines.length - (j + 1) > 1) { 118 | macroString += '/echo Macro #' + macroIndex + ' complete ' + soundEffect(options.stepSoundEffect, options.stepSoundEnabled) + '\n'; 119 | macroList.push({text: macroString, time: macroTime}); 120 | 121 | macroString = ''; 122 | macroLineCount = 0; 123 | macroTime = 0; 124 | macroIndex += 1; 125 | 126 | if (options.includeMacroLock) { 127 | macroString += '/macrolock\n'; 128 | macroLineCount++; 129 | } 130 | } 131 | } 132 | } 133 | 134 | if (macroLineCount > 0) { 135 | if (macroLineCount < MAX_LINES) { 136 | macroString += '/echo Macro #' + macroIndex + ' complete ' + soundEffect(options.finishSoundEffect, options.stepSoundEnabled) + '\n'; 137 | } 138 | macroList.push({text: macroString, time: macroTime}); 139 | } 140 | 141 | return macroList; 142 | } 143 | } 144 | })(); 145 | -------------------------------------------------------------------------------- /app/js/components/simulator-status.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.components', [ 6 | 'ffxivCraftOptWeb.services.bonusStats', 7 | 'ffxivCraftOptWeb.services.buffsdb' 8 | ]) 9 | .directive('simulatorStatus', factory); 10 | 11 | function factory() { 12 | return { 13 | restrict: 'E', 14 | templateUrl: 'components/simulator-status.html', 15 | scope: { 16 | crafter: '=', 17 | bonusStats: '=', 18 | recipe: '=', 19 | status: '=', 20 | valid: '&' 21 | }, 22 | controller: controller 23 | } 24 | } 25 | 26 | function controller($scope, $rootScope, $translate, _bonusStats, _buffsDatabase) { 27 | $scope.buffList = {}; 28 | 29 | $scope.buffName = buffName; 30 | 31 | $scope.$watchCollection("crafter", update); 32 | $scope.$watchCollection("bonusStats", update); 33 | $scope.$watchCollection("recipe", update); 34 | $scope.$watchCollection("status", update); 35 | 36 | $rootScope.$on('$translateChangeSuccess', updateBuffsLists); 37 | 38 | updateBuffsLists(); 39 | update(); 40 | 41 | ////////////////////////////////////////////////////////////////////////// 42 | 43 | function updateBuffsLists() { 44 | _buffsDatabase.buffs($translate.use(), 'Meal').then(function (buffs) { 45 | $scope.buffList.food = buffs; 46 | }); 47 | _buffsDatabase.buffs($translate.use(), 'Medicine').then(function (buffs) { 48 | $scope.buffList.medicine = buffs; 49 | }); 50 | } 51 | 52 | function update() { 53 | $scope.baseStats = $scope.crafter; 54 | 55 | if ($scope.bonusStats) { 56 | var buffStats = _bonusStats.newBonusStats(); 57 | if ($scope.bonusStats.food) { 58 | buffStats = _bonusStats.sumCrafterBonusStats( 59 | buffStats, 60 | _bonusStats.calculateBuffBonusStats($scope.crafter, $scope.bonusStats.food) 61 | ); 62 | } 63 | if ($scope.bonusStats.medicine) { 64 | buffStats = _bonusStats.sumCrafterBonusStats( 65 | buffStats, 66 | _bonusStats.calculateBuffBonusStats($scope.crafter, $scope.bonusStats.medicine) 67 | ); 68 | } 69 | $scope.buffStats = buffStats; 70 | 71 | var stats = _bonusStats.sumCrafterBonusStats($scope.crafter, $scope.buffStats); 72 | $scope.stats = _bonusStats.sumCrafterBonusStats(stats, $scope.bonusStats); 73 | } 74 | else { 75 | $scope.buffStats = _bonusStats.newBonusStats(); 76 | $scope.stats = $scope.crafter; 77 | } 78 | 79 | $scope.baseValues = $scope.status.baseValues; 80 | 81 | if ($scope.status.state) { 82 | $scope.durability = $scope.status.state.durability; 83 | $scope.condition = $scope.status.state.condition; 84 | $scope.progress = $scope.status.state.progress; 85 | $scope.quality = $scope.status.state.quality; 86 | $scope.cp = $scope.status.state.cp; 87 | $scope.maxCp = $scope.stats.cp + $scope.status.state.bonusMaxCp; 88 | } 89 | else { 90 | $scope.durability = $scope.recipe.durability; 91 | $scope.condition = ''; 92 | $scope.progress = 0; 93 | $scope.quality = 0; 94 | $scope.cp = $scope.stats.cp; 95 | $scope.maxCp = $scope.stats.cp; 96 | } 97 | 98 | $scope.progressPercent = Math.min(100, $scope.progress / $scope.recipe.difficulty * 100); 99 | $scope.qualityPercent = Math.min(100, $scope.quality / $scope.recipe.maxQuality * 100); 100 | $scope.cpPercent = Math.min(100, $scope.cp / $scope.maxCp * 100); 101 | 102 | $scope.hqPercent = $scope.status.state && $scope.status.state.hqPercent || 0; 103 | $scope.successPercent = $scope.status.state && $scope.status.state.successPercent || 0; 104 | 105 | } 106 | 107 | function buffName(buff) { 108 | var s = buff.name; 109 | if (buff.hq) { 110 | s += ' (HQ)'; 111 | } 112 | return s; 113 | } 114 | } 115 | })(); 116 | -------------------------------------------------------------------------------- /app/js/controllers/charimport.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.controllers') 6 | .controller('CharImportController', controller); 7 | 8 | function controller($scope, $modalInstance, server) { 9 | $scope.search = search; 10 | $scope.selectResult = selectResult; 11 | $scope.dismissSearchError = dismissSearchError; 12 | $scope.importCharacter = importCharacter; 13 | $scope.cancel = cancel; 14 | 15 | $scope.searchVars = { 16 | server: server, 17 | name: "" 18 | }; 19 | $scope.searchErrors = []; 20 | $scope.chars = {}; 21 | 22 | ////////////////////////////////////////////////////////////////////////// 23 | 24 | function search() { 25 | $scope.searching = true; 26 | delete $scope.results; 27 | delete $scope.selected; 28 | } 29 | 30 | function selectResult(id) { 31 | $scope.selected = id; 32 | $scope.selectedChar = $scope.chars[id]; 33 | } 34 | 35 | function dismissSearchError(index) { 36 | $scope.searchErrors.splice(index, 1); 37 | } 38 | 39 | function importCharacter() { 40 | cancelLoaders(); 41 | $modalInstance.close($scope.selectedChar); 42 | } 43 | 44 | function cancel() { 45 | cancelLoaders(); 46 | $modalInstance.dismiss('cancel'); 47 | } 48 | 49 | function cancelLoaders() { 50 | for (var char in $scope.chars) { 51 | if ($scope.chars.hasOwnProperty(char)) { 52 | if (char.loading) { 53 | char.loading.cancel(); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | })(); 61 | -------------------------------------------------------------------------------- /app/js/controllers/crafterstats.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.controllers') 6 | .controller('CrafterStatsController', controller); 7 | 8 | function controller($scope, $modal, _actionGroups, _allClasses, _actionsByName, _profile) { 9 | $scope.crafterActionClasses = crafterActionClasses; 10 | $scope.toggleAction = toggleAction; 11 | $scope.onTabSelect = onTabSelect; 12 | $scope.selectNoActions = selectNoActions; 13 | $scope.selectAllActions = selectAllActions; 14 | $scope.showCharImportModal = showCharImportModal; 15 | $scope.refreshChar = refreshChar; 16 | $scope.selectActionsByLevel = selectActionsByLevel; 17 | $scope.selectActionsGuaranteed = selectActionsGuaranteed; 18 | 19 | // Keep list of specialist actions 20 | var specialistActions = []; 21 | for (var i = 0; i < _actionGroups.length; i++) { 22 | var group = _actionGroups[i]; 23 | if (group.name === "Specialist") { 24 | specialistActions = group.actions; 25 | break; 26 | } 27 | } 28 | 29 | // Initialize tab names and initial active state 30 | $scope.tabs = []; 31 | for (var i = 0; i < _allClasses.length; i++) { 32 | var cls = _allClasses[i]; 33 | $scope.tabs.push({name: cls, active: cls === $scope.recipe.cls}); 34 | } 35 | 36 | $scope.$on('profile.loaded', onProfileLoaded); 37 | if (_profile.isLoaded()) { 38 | onProfileLoaded(); 39 | } 40 | 41 | ////////////////////////////////////////////////////////////////////////// 42 | 43 | function onProfileLoaded() { 44 | $scope.character = _profile.getCharacter(); 45 | } 46 | 47 | function crafterActionClasses(action, cls) { 48 | return { 49 | 'faded': !$scope.isActionSelected(action, cls), 50 | } 51 | } 52 | 53 | function toggleAction(action, cls) { 54 | var i = $scope.crafter.stats[cls].actions.indexOf(action); 55 | if (i >= 0) { 56 | $scope.crafter.stats[cls].actions.splice(i, 1); 57 | } 58 | else { 59 | $scope.crafter.stats[cls].actions.push(action); 60 | } 61 | } 62 | 63 | function onTabSelect(tab) { 64 | $scope.currentClass = tab.name; 65 | } 66 | 67 | function selectNoActions(cls) { 68 | var clsActions = $scope.crafter.stats[cls].actions; 69 | clsActions.splice(0, clsActions.length); 70 | } 71 | 72 | function selectAllActions(cls) { 73 | var clsActions = $scope.crafter.stats[cls].actions; 74 | clsActions.splice(0, clsActions.length); 75 | for (var i = 0; i < _actionGroups.length; i++) { 76 | var actions = _actionGroups[i].actions; 77 | for (var j = 0; j < actions.length; j++) { 78 | var action = _actionsByName[actions[j]]; 79 | clsActions.push(action.shortName); 80 | } 81 | } 82 | } 83 | 84 | function showCharImportModal() { 85 | var modalInstance = $modal.open({ 86 | templateUrl: 'modals/charimport.html', 87 | controller: 'CharImportController', 88 | windowClass: 'charimport-modal', 89 | resolve: { 90 | server: function () { 91 | return $scope.character && $scope.character.server || undefined; 92 | } 93 | } 94 | }); 95 | modalInstance.result.then(function (result) { 96 | console.log("import character:", result); 97 | importCharacter(result); 98 | }); 99 | } 100 | 101 | function importCharacter(char) { 102 | for (var className in char.classes) { 103 | if (char.classes.hasOwnProperty(className)) { 104 | var stats = $scope.crafter.stats[className]; 105 | var newStats = char.classes[className]; 106 | stats.level = newStats.level > 0 ? newStats.level : stats.level; 107 | stats.craftsmanship = newStats.craftsmanship > 0 ? newStats.craftsmanship : stats.craftsmanship; 108 | stats.control = newStats.control > 0 ? newStats.control : stats.control; 109 | stats.cp = newStats.cp > 0 ? newStats.cp : stats.cp; 110 | } 111 | } 112 | 113 | for (var i = 0; i < _allClasses.length; i++) { 114 | var cls = _allClasses[i]; 115 | selectActionsByLevel(cls); 116 | } 117 | 118 | $scope.character = { 119 | id: char.id, 120 | name: char.name, 121 | server: char.server 122 | }; 123 | 124 | $scope.profile.setCharacter($scope.character); 125 | } 126 | 127 | function refreshChar() { 128 | if (!$scope.character) return; 129 | 130 | $scope.character.refreshing = true; 131 | } 132 | 133 | function selectActionsByLevel(cls) { 134 | selectActions(cls, false); 135 | } 136 | 137 | function selectActionsGuaranteed(cls) { 138 | selectActions(cls, true); 139 | } 140 | 141 | function selectActions(cls, bySuccessRate) { 142 | var stats = $scope.crafter.stats[cls]; 143 | var selectedActions = []; 144 | 145 | for (var i = 0; i < _actionGroups.length; i++) { 146 | var actions = _actionGroups[i].actions; 147 | for (var j = 0; j < actions.length; j++) { 148 | var action = _actionsByName[actions[j]]; 149 | var actionClass = action.cls === "All" ? cls : action.cls; 150 | 151 | // Skip specialist actions if the class is not marked as a specialist 152 | if (specialistActions.indexOf(action.shortName) >= 0 && !$scope.crafter.stats[actionClass].specialist) { 153 | continue; 154 | } 155 | 156 | // If we care about level and the action's level is too high, skip it. 157 | if (action.level > $scope.crafter.stats[actionClass].level) continue; 158 | 159 | // If we care about successRate and the successRate is too low, skip it. 160 | if (bySuccessRate && action.successProbability < 1) continue; 161 | 162 | // Some actions are upgraded versions of others. 163 | // If this is one of the base versions and our level is high enough to use the upgraded version, skip it. 164 | if (action.shortName === "rapidSynthesis" && $scope.crafter.stats[actionClass].level >= 63) continue; 165 | if (action.shortName === "basicSynth" && $scope.crafter.stats[actionClass].level >= 31) continue; 166 | 167 | selectedActions.push(action.shortName); 168 | } 169 | } 170 | 171 | stats.actions = selectedActions; 172 | } 173 | 174 | } 175 | })(); 176 | -------------------------------------------------------------------------------- /app/js/controllers/macroimport.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.controllers') 6 | .controller('MacroImportController', controller); 7 | 8 | function controller($scope, $modalInstance, $translate, _actionsByName) { 9 | //noinspection AssignmentResultUsedJS 10 | var vm = $scope.vm = {}; 11 | 12 | vm.macroText = ""; 13 | 14 | vm.importMacro = importMacro; 15 | vm.cancel = cancel; 16 | vm.canImport = canImport; 17 | 18 | ////////////////////////////////////////////////////////////////////////// 19 | 20 | function importMacro() { 21 | var sequence = convertMacro(vm.macroText); 22 | if (sequence !== undefined) { 23 | $modalInstance.close({ 24 | sequence: sequence, 25 | }); 26 | } 27 | } 28 | 29 | function cancel() { 30 | $modalInstance.dismiss('cancel'); 31 | } 32 | 33 | function canImport() { 34 | return vm.macroText.trim().length > 0; 35 | } 36 | 37 | function convertMacro(macroString) { 38 | if (macroString === null || macroString === "") { 39 | return undefined; 40 | } 41 | 42 | var regex = /\/ac(tion)?\s+"(.*?)"\s*/g; 43 | var newSequence = []; 44 | var result; 45 | while (result = regex.exec(macroString)) { 46 | var action = result[2]; 47 | for (var key in _actionsByName) { 48 | var value = _actionsByName[key]; 49 | if (action === value.name || action === $translate.instant(value.name)) { 50 | newSequence.push(key); 51 | } 52 | } 53 | } 54 | 55 | if (newSequence.length === 0) { 56 | window.alert("Error: Invalid macro synth sequence."); 57 | return undefined; 58 | } 59 | 60 | return newSequence; 61 | } 62 | } 63 | 64 | })(); 65 | -------------------------------------------------------------------------------- /app/js/controllers/options.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.controllers') 6 | .controller('OptionsController', controller); 7 | 8 | function controller($scope, $modalInstance, pageState, sequenceSettings, solver, macroOptions) { 9 | $scope.onMonteCarloModeChange = onMonteCarloModeChange; 10 | $scope.save = save; 11 | $scope.cancel = cancel; 12 | 13 | $scope.pageState = pageState; 14 | $scope.sequenceSettings = angular.copy(sequenceSettings); 15 | $scope.solver = angular.copy(solver); 16 | $scope.macroOptions = angular.copy(macroOptions); 17 | 18 | ////////////////////////////////////////////////////////////////////////// 19 | 20 | function onMonteCarloModeChange() { 21 | if ($scope.sequenceSettings.monteCarloMode === 'macro') { 22 | $scope.sequenceSettings.useConditions = true; 23 | $scope.sequenceSettings.conditionalActionHandling = 'skipUnusable' 24 | } 25 | } 26 | 27 | function save() { 28 | $modalInstance.close({ 29 | sequenceSettings: $scope.sequenceSettings, 30 | solver: $scope.solver, 31 | macroOptions: $scope.macroOptions 32 | }); 33 | } 34 | 35 | function cancel() { 36 | $modalInstance.dismiss('cancel'); 37 | } 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /app/js/controllers/recipesearch.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.controllers') 6 | .controller('RecipeSearchController', controller); 7 | 8 | function controller($scope, $rootScope, $translate, $filter, _recipeLibrary) { 9 | $scope.updateRecipeSearchList = updateRecipeSearchList; 10 | $scope.recipeSelected = recipeSelected; 11 | $scope.onSearchKeyPress = onSearchKeyPress; 12 | $scope.onSearchKeyDown = onSearchKeyDown; 13 | 14 | $scope.recipeSearch = { 15 | loading: false, 16 | list: [], 17 | selected: 0, 18 | text: '', 19 | order: ['level', 'name'] 20 | }; 21 | 22 | $scope.$watch('recipeSearch.text', updateRecipeSearchList); 23 | $scope.$on('recipe.cls.changed', onRecipeClsChanged); 24 | $rootScope.$on('$translateChangeSuccess', updateRecipeSearchList); 25 | 26 | ////////////////////////////////////////////////////////////////////////// 27 | 28 | function onRecipeClsChanged() { 29 | $scope.recipeSearch.text = ''; 30 | updateRecipeSearchList(); 31 | } 32 | 33 | function updateRecipeSearchList() { 34 | $scope.recipeSearch.loading = true; 35 | var p = _recipeLibrary.recipesForClass($translate.use(), $scope.recipe.cls); 36 | p.then(function (recipes) { 37 | // Restrict recipes to crafter level 38 | recipes = $filter('filter')(recipes, {baseLevel: $scope.crafter.stats[$scope.recipe.cls].level}, 39 | function (recipeLevel, crafterLevel) { 40 | return !crafterLevel || crafterLevel >= recipeLevel - 5; 41 | }); 42 | 43 | // Then filter on text search, ignoring case and accents 44 | $scope.recipeSearch.list = 45 | $filter('filter')(recipes, {name: $scope.recipeSearch.text}, function (recipeName, recipeSearch) { 46 | if (recipeName === undefined || recipeSearch === undefined) 47 | return true; 48 | 49 | return recipeName.removeAccent().toUpperCase().indexOf(recipeSearch.removeAccent().toUpperCase()) >= 0; 50 | }); 51 | 52 | $scope.recipeSearch.selected = Math.min($scope.recipeSearch.selected, $scope.recipeSearch.list.length - 1); 53 | $scope.recipeSearch.loading = false; 54 | }, function (err) { 55 | console.error("Failed to retrieve recipes:", err); 56 | $scope.recipeSearch.list = []; 57 | $scope.recipeSearch.selected = -1; 58 | $scope.recipeSearch.loading = false; 59 | }); 60 | } 61 | 62 | function recipeSelected(name) { 63 | // force menu to close and search field to lose focus 64 | // improves behaviour on touch devices 65 | var root = document.getElementById('recipe-menu-root'); 66 | if (root.closeMenu) { // sometimes it's undefined? why??? 67 | root.closeMenu(); 68 | } 69 | document.getElementById('recipe-search-text').blur(); 70 | 71 | var cls = $scope.recipe.cls; 72 | var p = angular.copy(_recipeLibrary.recipeForClassByName($translate.use(), cls, name)); 73 | p.then(function (recipe) { 74 | recipe = angular.copy(recipe); 75 | recipe.cls = cls; 76 | recipe.startQuality = 0; 77 | $scope.$emit('recipe.selected', recipe); 78 | }, function (err) { 79 | console.error("Failed to load recipe:", err); 80 | }); 81 | } 82 | 83 | function onSearchKeyPress(event) { 84 | if (event.which == 13) { 85 | event.preventDefault(); 86 | recipeSelected($scope.recipeSearch.list[$scope.recipeSearch.selected].name); 87 | } 88 | } 89 | 90 | function onSearchKeyDown(event) { 91 | if (event.which === 40) { 92 | // down 93 | $scope.recipeSearch.selected = Math.min($scope.recipeSearch.selected + 1, $scope.recipeSearch.list.length - 1); 94 | document.getElementById('recipeSearchElement' + $scope.recipeSearch.selected).scrollIntoViewIfNeeded(false); 95 | } 96 | else if (event.which === 38) { 97 | // up 98 | $scope.recipeSearch.selected = Math.max($scope.recipeSearch.selected - 1, 0); 99 | document.getElementById('recipeSearchElement' + $scope.recipeSearch.selected).scrollIntoViewIfNeeded(false); 100 | } 101 | } 102 | } 103 | 104 | })(); 105 | -------------------------------------------------------------------------------- /app/js/controllers/sequenceeditor.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.controllers') 6 | .controller('SequenceEditorController', controller); 7 | 8 | function controller($scope, $http, $state, _actionGroups, _actionsByName, _simulator, _getActionImagePath, _iActionClassSpecific, $translate) { 9 | $scope.actionGroups = _actionGroups; 10 | $scope.allActions = _actionsByName; 11 | $scope.getActionImagePath = _getActionImagePath; 12 | $scope.iActionClassSpecific = _iActionClassSpecific; 13 | 14 | $scope.origSequence = []; 15 | $scope.editSequence = []; 16 | $scope.availableActions = []; 17 | $scope.recipe = {}; 18 | 19 | $scope.isActionSelected = isActionSelected; 20 | $scope.actionTableClasses = actionTableClasses; 21 | $scope.actionClasses = actionClasses; 22 | $scope.dropAction = dropAction; 23 | $scope.addAction = addAction; 24 | $scope.removeAction = removeAction; 25 | $scope.isValidSequence = isValidSequence; 26 | $scope.isSequenceDirty = isSequenceDirty; 27 | $scope.clear = clear; 28 | $scope.revert = revert; 29 | $scope.save = save; 30 | $scope.cancel = cancel; 31 | 32 | $scope.$on('sequence.editor.init', onSequenceEditorInit); 33 | $scope.$on('$stateChangeStart', onStateChangeStart); 34 | 35 | ////////////////////////////////////////////////////////////////////////// 36 | 37 | function onSequenceEditorInit(event, origSequence, recipe, crafterStats, bonusStats, sequenceSettings) { 38 | $scope.origSequence = origSequence; 39 | $scope.editSequence = angular.copy(origSequence); 40 | $scope.availableActions = crafterStats.actions; 41 | $scope.recipe = recipe; 42 | 43 | $scope.unwatchSequence = $scope.$watchCollection('editSequence', function () { 44 | $scope.$emit('update.sequence', $scope.editSequence); 45 | }); 46 | } 47 | 48 | function onStateChangeStart(event) { 49 | if ($scope.editingSequence && $scope.isSequenceDirty()) { 50 | if (!window.confirm($translate.instant('ABANDON_CHANGES'))) { 51 | event.preventDefault(); 52 | } 53 | } 54 | } 55 | 56 | function isActionSelected(action) { 57 | return $scope.availableActions.indexOf(action) >= 0; 58 | } 59 | 60 | function actionTableClasses(action, cls) { 61 | return { 62 | 'action-no-cp': $scope.simulatorStatus.state && 63 | (_actionsByName[action].cpCost > $scope.simulatorStatus.state.cp), 64 | 'faded-icon': !isActionSelected(action) 65 | }; 66 | } 67 | 68 | function actionClasses(action, cls, index) { 69 | var wastedAction = $scope.simulatorStatus.state && (index + 1 > $scope.simulatorStatus.state.lastStep); 70 | var cpExceeded = $scope.simulatorStatus.state && _actionsByName[action].cpCost > $scope.simulatorStatus.state.cp; 71 | return { 72 | 'faded-icon': !isActionSelected(action, cls), 73 | 'wasted-action': wastedAction, 74 | 'action-no-cp': wastedAction && cpExceeded 75 | }; 76 | } 77 | 78 | function dropAction(dragEl, dropEl) { 79 | var drag = angular.element(document.getElementById(dragEl)); 80 | var drop = angular.element(document.getElementById(dropEl)); 81 | var newAction = drag.attr('data-new-action'); 82 | var dropIndex = parseInt(drop.attr('data-index')); 83 | 84 | if (newAction) { 85 | 86 | // insert new action into the drop position 87 | $scope.editSequence.splice(dropIndex, 0, newAction); 88 | } 89 | else { 90 | var dragIndex = parseInt(drag.attr('data-index')); 91 | 92 | // do nothing if dropped on itself 93 | if (dragIndex == dropIndex) return; 94 | 95 | // insert dragged action into the drop position 96 | $scope.editSequence.splice(dropIndex, 0, $scope.editSequence[dragIndex]); 97 | 98 | // remove dragged action from its original position 99 | if (dropIndex > dragIndex) { 100 | $scope.editSequence.splice(dragIndex, 1); 101 | } 102 | else { 103 | $scope.editSequence.splice(dragIndex + 1, 1); 104 | } 105 | } 106 | 107 | $scope.$apply(); 108 | } 109 | 110 | function addAction(action) { 111 | $scope.editSequence.push(action); 112 | } 113 | 114 | function removeAction(index) { 115 | $scope.editSequence.splice(index, 1) 116 | } 117 | 118 | function isValidSequence(sequence, cls) { 119 | return sequence.every(function (action) { 120 | return isActionSelected(action, cls); 121 | }); 122 | } 123 | 124 | function isSequenceDirty() { 125 | return !angular.equals($scope.editSequence, $scope.origSequence); 126 | } 127 | 128 | function clear() { 129 | $scope.editSequence = []; 130 | } 131 | 132 | function revert() { 133 | $scope.editSequence = angular.copy($scope.origSequence); 134 | } 135 | 136 | function save() { 137 | if ($scope.unwatchSequence) { 138 | $scope.unwatchSequence(); 139 | } 140 | 141 | $scope.$emit('sequence.editor.close'); 142 | } 143 | 144 | function cancel() { 145 | $scope.$emit('update.sequence', $scope.origSequence); 146 | 147 | if ($scope.unwatchSequence) { 148 | $scope.unwatchSequence(); 149 | } 150 | 151 | $scope.$emit('sequence.editor.close'); 152 | } 153 | } 154 | 155 | })(); 156 | -------------------------------------------------------------------------------- /app/js/controllers/settingsimport.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.controllers') 6 | .controller('SettingsImportController', controller); 7 | 8 | function controller($scope, $window, $translate) { 9 | //noinspection AssignmentResultUsedJS 10 | var vm = $scope.vm = {}; 11 | 12 | vm.generateFile = generateFile; 13 | vm.handleDrop = handleDrop; 14 | 15 | ////////////////////////////////////////////////////////////////////////// 16 | 17 | function generateFile() { 18 | var zip = new JSZip(); 19 | zip.file("settings.json", generateExportText()); 20 | zip.generateAsync({type:"blob"}) 21 | .then(function(content) { 22 | // see FileSaver.js 23 | saveAs(content, "settings.zip"); 24 | }); 25 | } 26 | 27 | function handleDrop(file) { 28 | JSZip.loadAsync(file.content) 29 | .then(function (zip) { 30 | var settingsJsonFile = zip.file('settings.json'); 31 | if (!settingsJsonFile) { 32 | throw new Error($translate.instant('SETTINGS_NOT_FOUND')); 33 | } 34 | settingsJsonFile.async('text') 35 | .then(function (text) { 36 | $scope.$apply(function () { 37 | try { 38 | importSettings(text); 39 | } 40 | catch (err) { 41 | $window.alert($translate.instant('INVALID_FILE') + '\n\n' + err.message); 42 | } 43 | }); 44 | }) 45 | .catch(function (err) { 46 | $window.alert($translate.instant('INVALID_FILE') + '\n\n' + err.message); 47 | }); 48 | }) 49 | .catch(function (err) { 50 | $window.alert($translate.instant('INVALID_FILE') + '\n\n' + err.message); 51 | }); 52 | } 53 | 54 | function importSettings(text) { 55 | var data; 56 | data = JSON.parse(text); 57 | if (!$window.confirm($translate.instant('CONFIRM_IMPORT_SETTINGS'))) { 58 | return; 59 | } 60 | console.log('Importing settings into local storage:', data); 61 | for (var key in data) { 62 | if (data.hasOwnProperty(key)) { 63 | localStorage[key] = data[key]; 64 | } 65 | } 66 | $window.alert($translate.instant('SETTINGS_IMPORTED')); 67 | $window.location.reload(); 68 | } 69 | 70 | function generateExportText() { 71 | var settings = {}; 72 | 73 | var keys = [ 74 | 'NG_TRANSLATE_LANG_KEY', 75 | 'crafterStats', 76 | 'pageStage_v2', 77 | 'synths', 78 | 'character' 79 | ]; 80 | for (var i = 0; i < keys.length; i++) { 81 | var key = keys[i]; 82 | settings[key] = localStorage[key]; 83 | } 84 | 85 | return JSON.stringify(settings); 86 | } 87 | } 88 | 89 | })(); 90 | -------------------------------------------------------------------------------- /app/js/polyfills.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | // scrollIntoViewIfNeeded polyfill for Firefox and IE 5 | // Based on https://gist.github.com/hsablonniere/2581101 6 | if (!Element.prototype.scrollIntoViewIfNeeded) { 7 | Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) { 8 | centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded; 9 | 10 | var parent = this.parentNode, 11 | parentComputedStyle = window.getComputedStyle(parent, null), 12 | parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')), 13 | parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')), 14 | overTop = this.offsetTop - parent.offsetTop < parent.scrollTop, 15 | overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight), 16 | overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft, 17 | overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth); 18 | 19 | if (centerIfNeeded) { 20 | if (overTop || overBottom) { 21 | parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2; 22 | } 23 | 24 | if (overLeft || overRight) { 25 | parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2; 26 | } 27 | } 28 | else { 29 | if (overTop) { 30 | parent.scrollTop = this.offsetTop - parent.offsetTop - parentBorderTopWidth; 31 | } 32 | 33 | if (overBottom) { 34 | parent.scrollTop = this.offsetTop - parent.offsetTop - parentBorderTopWidth - parent.clientHeight + this.clientHeight; 35 | } 36 | 37 | if (overLeft) { 38 | parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parentBorderLeftWidth; 39 | } 40 | 41 | if (overRight) { 42 | parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parentBorderLeftWidth - parent.clientWidth + this.clientWidth; 43 | } 44 | } 45 | }; 46 | } 47 | 48 | // Remove Accent from source (ex: ùéèûà will be returned as ueeua). 49 | // Use to compare string without accents 50 | // --------------------------------------------------- 51 | String.prototype.removeAccent = function(){ 52 | var accent = [ 53 | /[\300-\306]/g, /[\340-\346]/g, // A, a 54 | /[\310-\313]/g, /[\350-\353]/g, // E, e 55 | /[\314-\317]/g, /[\354-\357]/g, // I, i 56 | /[\322-\330]/g, /[\362-\370]/g, // O, o 57 | /[\331-\334]/g, /[\371-\374]/g, // U, u 58 | /[\321]/g, /[\361]/g, // N, n 59 | /[\307]/g, /[\347]/g, // C, c 60 | ], 61 | noaccent = ['A','a','E','e','I','i','O','o','U','u','N','n','C','c']; 62 | 63 | var str = this; 64 | for (var i = 0; i < accent.length; i++){ 65 | str = str.replace(accent[i], noaccent[i]); 66 | } 67 | 68 | return str; 69 | }; 70 | })(); 71 | -------------------------------------------------------------------------------- /app/js/routes.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb') 6 | .config(Routes); 7 | 8 | function Routes($stateProvider, $urlRouterProvider) { 9 | $urlRouterProvider.otherwise('/simulator'); 10 | 11 | $stateProvider 12 | .state('crafter-attributes', { 13 | url: '/crafter-attributes', 14 | templateUrl: 'views/crafter-attributes.html', 15 | controller: 'CrafterStatsController' 16 | }) 17 | .state('simulator', { 18 | url: '/simulator', 19 | templateUrl: 'views/simulator.html', 20 | controller: 'SimulatorController' 21 | }) 22 | .state('recipe', { 23 | url: '/recipe?class&name&lang', 24 | onEnter: function ($transition$, $state, $rootScope, $translate, _recipeLibrary) { 25 | var params = $transition$.params(); 26 | var cls = params.class; 27 | var name = params.name; 28 | var lang = params.lang || $translate.use(); 29 | return _recipeLibrary.recipeForClassByName(lang, cls, name) 30 | .then(function (recipe) { 31 | if (lang != $translate.use()) $translate.use(lang); 32 | recipe = angular.copy(recipe); 33 | recipe.cls = cls; 34 | recipe.startQuality = 0; 35 | $rootScope.$broadcast('recipe.selected', recipe); 36 | return $state.target('simulator'); 37 | }) 38 | .catch(function (err) { 39 | console.error("Failed to load recipe: class={0} name={1} lang={2}".format(cls, name, lang), err); 40 | return $state.target('simulator'); 41 | }); 42 | } 43 | }) 44 | .state('solver', { 45 | url: '/solver', 46 | templateUrl: 'views/solver.html', 47 | controller: 'SolverController', 48 | params: { 49 | autoStart: null 50 | } 51 | }) 52 | .state('instructions', { 53 | url: '/instructions', 54 | templateUrl: 'views/instructions.html' 55 | }) 56 | .state('about', { 57 | url: '/about', 58 | templateUrl: 'views/about.html' 59 | }); 60 | } 61 | })(); 62 | -------------------------------------------------------------------------------- /app/js/seededrandom.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | // From http://indiegamr.com/generate-repeatable-random-numbers-in-js/ 5 | 6 | Math.seed = Date.now(); 7 | Math._originalRandom = Math.random; 8 | Math.random = function(max, min) { 9 | max = max || 1; 10 | min = min || 0; 11 | 12 | Math.seed = (Math.seed * 9301 + 49297) % 233280; 13 | var rnd = Math.seed / 233280; 14 | 15 | return min + rnd * (max - min); 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /app/js/services/bonusstats.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module("ffxivCraftOptWeb.services.bonusStats", []) 6 | .factory("_bonusStats", factory); 7 | 8 | function factory() { 9 | return { 10 | newBonusStats: newBonusStats, 11 | addCrafterBonusStats: addCrafterBonusStats, 12 | sumCrafterBonusStats: sumCrafterBonusStats, 13 | calculateBuffBonusStats: calculateBuffBonusStats, 14 | addRecipeBonusStats: addRecipeBonusStats 15 | } 16 | } 17 | 18 | function newBonusStats() { 19 | return { 20 | craftsmanship: 0, 21 | control: 0, 22 | cp: 0, 23 | startQuality: 0, 24 | food: {}, 25 | medicine: {} 26 | } 27 | } 28 | 29 | function addCrafterBonusStats(crafter, bonusStats) { 30 | var r = angular.copy(crafter); 31 | if (bonusStats.food) { 32 | r = sumCrafterBonusStats(r, calculateBuffBonusStats(crafter, bonusStats.food)); 33 | } 34 | if (bonusStats.medicine) { 35 | r = sumCrafterBonusStats(r, calculateBuffBonusStats(crafter, bonusStats.medicine)); 36 | } 37 | r = sumCrafterBonusStats(r, bonusStats); 38 | return r; 39 | } 40 | 41 | function sumCrafterBonusStats(a, b) { 42 | var r = angular.copy(a); 43 | r.craftsmanship += b.craftsmanship; 44 | r.control += b.control; 45 | r.cp += b.cp; 46 | return r; 47 | } 48 | 49 | function calculateBuffBonusStats(crafter, buff) { 50 | var r = {}; 51 | r.craftsmanship = calcPercentMaxBonus(crafter.craftsmanship, buff.craftsmanship_percent, buff.craftsmanship_value); 52 | r.control = calcPercentMaxBonus(crafter.control, buff.control_percent, buff.control_value); 53 | r.cp = calcPercentMaxBonus(crafter.cp, buff.cp_percent, buff.cp_value); 54 | return r; 55 | } 56 | 57 | function calcPercentMaxBonus(base, percent, max) { 58 | if (!percent || !max) return 0; 59 | return Math.min(max, Math.floor(base * percent / 100)); 60 | } 61 | 62 | function addRecipeBonusStats(recipe, bonusStats) { 63 | var r = angular.copy(recipe); 64 | r.startQuality += bonusStats.startQuality; 65 | return r; 66 | } 67 | })(); 68 | -------------------------------------------------------------------------------- /app/js/services/buffsdb.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.services.buffsdb', ['ffxivCraftOptWeb.services.locale']) 6 | .service('_buffsDatabase', BuffsDatabaseService); 7 | 8 | function BuffsDatabaseService($http, $q, _languages) { 9 | this.$http = $http; 10 | this.$q = $q; 11 | this._languages = _languages; 12 | this.cache = {} 13 | } 14 | 15 | BuffsDatabaseService.$inject = ['$http', '$q', '_languages']; 16 | 17 | BuffsDatabaseService.prototype.buffs = function (lang, type) { 18 | if (!angular.isDefined(lang)) lang = 'en'; 19 | if (!this._languages[lang]) { 20 | return this.$q.reject(new Error('invalid language: ' + lang)); 21 | } 22 | var key = cache_key(lang, type); 23 | var promise = this.cache[key]; 24 | if (!promise) { 25 | promise = this.$http.get('data/buffs/' + type + '.json').then( 26 | function (r) { 27 | var result = r.data.map(buffForLang.bind(this, lang)); 28 | result.sort(function (a, b) { 29 | if (a.name < b.name) return -1; 30 | else if (a.name > b.name) return 1; 31 | if (!a.hq && b.hq) return -1; 32 | else if (a.hq && !b.hq) return 1; 33 | return 0; 34 | }); 35 | result = result.map(function (buff) { 36 | var r = angular.copy(buff); 37 | r.id = r.name + (r.hq ? ':hq' : ''); 38 | return r; 39 | }); 40 | result.unshift(); 41 | return result; 42 | } 43 | ); 44 | this.cache[key] = promise; 45 | } 46 | return promise; 47 | }; 48 | 49 | function buffForLang(lang, buff) { 50 | if (typeof buff.name !== 'object') return buff; 51 | 52 | var r = {}; 53 | for (var p in buff) { 54 | if (buff.hasOwnProperty(p) && p !== "name") { 55 | r[p] = buff[p]; 56 | } 57 | } 58 | r.name = buff.name[lang] || buff.name['en']; 59 | return r; 60 | } 61 | 62 | 63 | function cache_key(lang, type) { 64 | return lang + ':' + type; 65 | } 66 | })(); 67 | -------------------------------------------------------------------------------- /app/js/services/locale.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.services.locale', []) 6 | .constant('_languages', { 7 | ja: '日本語', 8 | en: 'English', 9 | de: 'Deutsch', 10 | fr: 'Français', 11 | cn: '简体中文', 12 | ko: '한국어', 13 | ar: 'عربى', 14 | es: 'Español' 15 | }) 16 | })(); 17 | -------------------------------------------------------------------------------- /app/js/services/localstorage.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.services.storage', []) 6 | .service('_localStorage', LocalStorageService); 7 | 8 | function LocalStorageService() { 9 | } 10 | 11 | LocalStorageService.prototype.get = function (key) { 12 | var value = localStorage.getItem(key); 13 | if (value !== null) value = JSON.parse(value); 14 | return value; 15 | }; 16 | 17 | LocalStorageService.prototype.put = function (key, value) { 18 | if (value === undefined || value === null) { 19 | throw new TypeError('value may not be undefined or null'); 20 | } 21 | localStorage.setItem(key, JSON.stringify(value)); 22 | }; 23 | 24 | LocalStorageService.prototype.remove = function (key) { 25 | localStorage.removeItem(key); 26 | }; 27 | 28 | LocalStorageService.prototype.hasKey = function (key) { 29 | return localStorage.getItem(key) !== null; 30 | }; 31 | 32 | })(); 33 | -------------------------------------------------------------------------------- /app/js/services/profile.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.services.profile', []) 6 | .service('_profile', ProfileService); 7 | 8 | function ProfileService($timeout, _allClasses, _actionsByName) { 9 | this.$timeout = $timeout; 10 | this._allClasses = _allClasses; 11 | this._actionsByName = _actionsByName; 12 | return this; 13 | } 14 | 15 | ProfileService.$inject = ['$timeout', '_allClasses', '_actionsByName']; 16 | 17 | ProfileService.prototype.useStorage = function (storage) { 18 | if (storage === undefined || storage === null) { 19 | throw new TypeError('storage may not be undefined or null'); 20 | } 21 | this.storage = storage; 22 | }; 23 | 24 | ProfileService.prototype.load = function () { 25 | return this.$timeout(function () { 26 | this.synths = this.storage.get('synths') || {}; 27 | 28 | var modified = false; 29 | var name; 30 | 31 | if (Object.keys(this.synths).length === 0) { 32 | var oldSavedSettings = this.storage.get('savedSettings'); 33 | if (oldSavedSettings) { 34 | // Import old saved settings as synths 35 | for (name in oldSavedSettings) { 36 | this.synths[name] = oldSavedSettings[name]; 37 | } 38 | modified = true; 39 | this.storage.remove('savedSettings'); 40 | } 41 | } 42 | 43 | // Upgrade existing synths with new settings 44 | for (name in this.synths) { 45 | var synth = this.synths[name]; 46 | for (var i = 0; i < synth.sequence.length; i++){ 47 | if (synth.sequence[i] === "standardSynthesis") synth.sequence[i] = "basicSynth2"; 48 | } 49 | } 50 | 51 | if (this.storage.hasKey('crafterStats')) { 52 | this.crafterStats = this.storage.get('crafterStats'); 53 | } 54 | else { 55 | var oldCrafterSettings = this.storage.get('settings.crafter'); 56 | if (oldCrafterSettings) { 57 | // Import old crafter stats 58 | this.crafterStats = oldCrafterSettings.stats; 59 | modified = true; 60 | this.storage.remove('settings.crafter'); 61 | } 62 | else { 63 | // Initialize default stats 64 | var crafterStats = {}; 65 | 66 | for (var i = 0; i < this._allClasses.length; i++) { 67 | var c = this._allClasses[i]; 68 | crafterStats[c] = { 69 | level: 1, 70 | craftsmanship: 24, 71 | control: 0, 72 | cp: 180, 73 | actions: ['basicSynth'] 74 | } 75 | } 76 | 77 | this.crafterStats = crafterStats; 78 | } 79 | } 80 | 81 | for (var i = 0; i < this._allClasses.length; i++) { 82 | var c = this._allClasses[i]; 83 | var sanitizedActions = []; 84 | for (var j = 0; j < this.crafterStats[c].actions.length; j++) { 85 | var action = this.crafterStats[c].actions[j]; 86 | if (!angular.isDefined(action)) continue; 87 | var info = this._actionsByName[action]; 88 | if (!angular.isDefined(info)) continue; 89 | sanitizedActions.push(action); 90 | } 91 | 92 | if (!angular.equals(sanitizedActions, this.crafterStats[c].actions)) { 93 | this.crafterStats[c].actions = sanitizedActions; 94 | modified = true; 95 | } 96 | } 97 | 98 | if (modified) { 99 | this.persist(); 100 | } 101 | 102 | return this; 103 | }.bind(this)); 104 | }; 105 | 106 | ProfileService.prototype.userInfo = function () { 107 | return null; 108 | }; 109 | 110 | ProfileService.prototype.synthNames = function () { 111 | return Object.keys(this.synths); 112 | }; 113 | 114 | ProfileService.prototype.loadSynth = function (name) { 115 | return angular.copy(this.synths[name]); 116 | }; 117 | 118 | ProfileService.prototype.saveSynth = function (name, synth) { 119 | this.synths[name] = angular.copy(synth); 120 | this.persist(); 121 | }; 122 | 123 | ProfileService.prototype.deleteSynth = function (name) { 124 | delete this.synths[name]; 125 | this.persist(); 126 | }; 127 | 128 | ProfileService.prototype.renameSynth = function (oldName, newName) { 129 | this.synths[newName] = this.synths[oldName]; 130 | delete this.synths[oldName]; 131 | this.persist(); 132 | }; 133 | 134 | ProfileService.prototype.bindCrafterStats = function ($scope, expr) { 135 | var self = this; 136 | var stats = $scope.$eval(expr); 137 | for (var cls in this.crafterStats) { 138 | stats[cls] = this.crafterStats[cls]; 139 | $scope.$watchCollection(expr + '.' + cls, function() { 140 | self.persist(); 141 | }); 142 | $scope.$watchCollection(expr + '.' + cls + '.actions', function() { 143 | self.persist(); 144 | }); 145 | } 146 | }; 147 | 148 | ProfileService.prototype.getCrafterStats = function () { 149 | return angular.copy(this.crafterStats); 150 | }; 151 | 152 | ProfileService.prototype.persist = function() { 153 | this.storage.put('synths', this.synths); 154 | this.storage.put('crafterStats', this.crafterStats); 155 | }; 156 | 157 | ProfileService.prototype.setCharacter = function (obj) { 158 | this.storage.put('character', obj); 159 | }; 160 | 161 | ProfileService.prototype.getCharacter = function () { 162 | return this.storage.get('character'); 163 | }; 164 | 165 | ProfileService.prototype.isLoaded = function () { 166 | return this.synths || this.crafterStats; 167 | }; 168 | 169 | })(); 170 | -------------------------------------------------------------------------------- /app/js/services/recipelibrary.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.services.recipelibrary', ['ffxivCraftOptWeb.services.actions', 'ffxivCraftOptWeb.services.locale']) 6 | .service('_recipeLibrary', RecipeLibraryService); 7 | 8 | function RecipeLibraryService($http, $q, _allClasses, _languages) { 9 | this.$http = $http; 10 | this.$q = $q; 11 | this._allClasses = _allClasses; 12 | this._languages = _languages; 13 | this.cache = {} 14 | } 15 | 16 | RecipeLibraryService.$inject = ['$http', '$q', '_allClasses', '_languages']; 17 | 18 | RecipeLibraryService.prototype.recipesForClass = function(lang, cls) { 19 | if (!angular.isDefined(lang)) lang = 'en'; 20 | if (!this._languages[lang]) { 21 | return this.$q.reject(new Error('invalid language: ' + lang)); 22 | } 23 | if (this._allClasses.indexOf(cls) < 0) { 24 | return this.$q.reject(new Error('invalid class: ' + cls)); 25 | } 26 | var key = cache_key(lang, cls); 27 | var promise = this.cache[key]; 28 | if (!promise) { 29 | promise = this.$http.get('data/recipedb/' + cls + '.json').then( 30 | function (r) { 31 | var result = r.data.map(recipeForLang.bind(this, lang)); 32 | result.sort(function (a, b) { 33 | var diff = a.level - b.level; 34 | if (diff !== 0) return diff; 35 | if (a.name < b.name) return -1; 36 | else if (a.name > b.name) return 1; 37 | return 0; 38 | }); 39 | return result; 40 | } 41 | ); 42 | this.cache[key] = promise; 43 | } 44 | return promise; 45 | }; 46 | 47 | RecipeLibraryService.prototype.recipeForClassByName = function (lang, cls, name) { 48 | if (!angular.isDefined(lang)) lang = 'en'; 49 | return this.recipesForClass(lang, cls).then( 50 | function (recipes) { 51 | for (var i = 0; i < recipes.length; i++) { 52 | var recipe = recipes[i]; 53 | if (recipe.name == name) { 54 | return recipe; 55 | } 56 | } 57 | return this.$q.reject(new Error("could not find recipe")); 58 | }.bind(this) 59 | ); 60 | }; 61 | 62 | function recipeForLang(lang, recipe) { 63 | var r = {}; 64 | for (var p in recipe) { 65 | if (recipe.hasOwnProperty(p) && p != "name") { 66 | r[p] = recipe[p]; 67 | } 68 | } 69 | r.name = recipe.name[lang]; 70 | return r; 71 | } 72 | 73 | function cache_key(lang, cls) { 74 | return lang + ":" + cls 75 | } 76 | })(); 77 | -------------------------------------------------------------------------------- /app/js/services/simulator.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.services.simulator', []) 6 | .service('_simulator', SimulatorService); 7 | 8 | function SimulatorService($timeout) { 9 | this.$timeout = $timeout; 10 | 11 | var worker = new Worker('js/simulationworker.js'); 12 | 13 | var self = this; 14 | worker.onmessage = function (e) { 15 | if (e.data.success) { 16 | self.$timeout(function () { 17 | self.callbacks[e.data.id].success(e.data.success); 18 | }); 19 | } 20 | else if (e.data.error) { 21 | self.$timeout(function () { 22 | self.callbacks[e.data.id].error(e.data.error); 23 | }); 24 | } 25 | else { 26 | console.error('unexpected message from simulation worker: %O', e.data); 27 | if (e.data && e.data.id) { 28 | self.$timeout(function () { 29 | self.callbacks[e.data.id].error({log: '', error: 'unexpected message from simulation worker: ' + e.data}); 30 | }); 31 | } 32 | } 33 | }; 34 | 35 | this.worker = worker; 36 | 37 | this.currentId = 0; 38 | this.callbacks = {}; 39 | } 40 | 41 | SimulatorService.$inject = ['$timeout']; 42 | 43 | SimulatorService.prototype.runMonteCarloSim = function(settings, success, error) { 44 | if (settings.sequence.length <= 0) { 45 | error({log: '', error: 'empty sequence'}); 46 | return; 47 | } 48 | if (settings.recipe.startQuality === undefined) { 49 | settings.recipe = angular.copy(settings.recipe); 50 | settings.recipe.startQuality = 0; 51 | } 52 | 53 | var id = this.nextId(); 54 | 55 | this.callbacks[id] = { 56 | success: success, 57 | error: error 58 | }; 59 | 60 | this.worker.postMessage({ 61 | id: id, 62 | type: 'montecarlo', 63 | settings: settings 64 | }); 65 | }; 66 | 67 | SimulatorService.prototype.runProbabilisticSim = function (settings, success, error) { 68 | if (settings.sequence.length <= 0) { 69 | error({log: '', error: 'empty sequence'}); 70 | return; 71 | } 72 | if (settings.recipe.startQuality === undefined) { 73 | settings.recipe = angular.copy(settings.recipe); 74 | settings.recipe.startQuality = 0; 75 | } 76 | 77 | var id = this.nextId(); 78 | 79 | this.callbacks[id] = { 80 | success: success, 81 | error: error 82 | }; 83 | 84 | this.worker.postMessage({ 85 | id: id, 86 | type: 'prob', 87 | settings: settings 88 | }); 89 | }; 90 | 91 | SimulatorService.prototype.calculateBaseValues = function (settings, success, error) { 92 | var id = this.nextId(); 93 | this.callbacks[id] = { 94 | success: success, 95 | error: error 96 | }; 97 | this.worker.postMessage({ 98 | id: id, 99 | type: 'baseValues', 100 | settings: settings, 101 | }); 102 | }; 103 | 104 | SimulatorService.prototype.nextId = function () { 105 | this.currentId += 1; 106 | return this.currentId; 107 | }; 108 | })(); 109 | -------------------------------------------------------------------------------- /app/js/services/tooltips.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.services.tooltips', []) 6 | .service('_tooltips', TooltipsService); 7 | 8 | function TooltipsService($rootScope, $q, $translate, _allActions, _allClasses, _actionsByName) { 9 | this.$rootScope = $rootScope; 10 | this.$q = $q; 11 | this.$translate = $translate; 12 | this._allActions = _allActions; 13 | this._allClasses = _allClasses; 14 | this._actionsByName = _actionsByName; 15 | 16 | this.actionTooltips = {}; 17 | } 18 | 19 | TooltipsService.$inject = ['$rootScope', '$q', '$translate', '_allActions', '_allClasses', '_actionsByName']; 20 | 21 | TooltipsService.prototype.loadTooltips = function (lang) { 22 | this.actionTooltips = {}; 23 | 24 | for (var i = 0; i < this._allActions.length; i++) { 25 | var action = this._allActions[i]; 26 | if (action.skillID) { 27 | if (action.cls === 'All') { 28 | for (var j = 0; j < this._allClasses.length; j++) { 29 | var cls = this._allClasses[j]; 30 | this.actionTooltips[cls + action.shortName] = this.renderTooltip(action); 31 | } 32 | } 33 | else { 34 | this.actionTooltips[action.cls + action.shortName] = this.renderTooltip(action); 35 | } 36 | } 37 | } 38 | 39 | this.$rootScope.$broadcast("tooltipCacheUpdated"); 40 | 41 | var deferred = this.$q.defer(); 42 | var promise = deferred.promise; 43 | deferred.resolve(true); 44 | return promise; 45 | }; 46 | 47 | TooltipsService.prototype.renderTooltip = function (action) { 48 | var T = this.$translate.instant; 49 | var efficiency = (action.qualityIncreaseMultiplier > 0 ? action.qualityIncreaseMultiplier : action.progressIncreaseMultiplier) * 100; 50 | var successRate = action.successProbability * 100; 51 | return "" 52 | + "
" + T(action.name) + " (" + T("LEVEL") + " " + action.level + ")
\n" 53 | + "
" 54 | + "" + T("CP_COST") + ":" + action.cpCost + "
\n" 55 | + "" + T("DURABILITY_COST") + ":" + action.durabilityCost + "
\n" 56 | + "" + T("EFFICIENCY") + ":" + efficiency + "%
\n" 57 | + "" + T("SUCCESS_RATE") + ":" + successRate + "%
\n" 58 | + "
" 59 | }; 60 | 61 | })(); 62 | -------------------------------------------------------------------------------- /app/js/services/translatelocalstorage.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.services.translateLocalStorage', []) 6 | .factory('_translateLocalStorage', translateLocalStorage); 7 | 8 | function translateLocalStorage() { 9 | return { 10 | put: put, 11 | get: get 12 | }; 13 | 14 | ////////////////////////////////////////////////////////////////////////// 15 | 16 | function get(name) { 17 | return localStorage[name]; 18 | } 19 | 20 | function put(name, value) { 21 | localStorage[name] = value; 22 | } 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /app/js/simulationworker.js: -------------------------------------------------------------------------------- 1 | importScripts('../lib/string/String.js'); 2 | importScripts('actions.js'); 3 | importScripts('ffxivcraftmodel.js'); 4 | importScripts('seededrandom.js'); 5 | 6 | self.onmessage = function(e) { 7 | try { 8 | switch (e.data.type) { 9 | case 'prob': 10 | runProbablisticSim(e.data.id, e.data.settings); 11 | break; 12 | case 'montecarlo': 13 | runMonteCarloSim(e.data.id, e.data.settings); 14 | break; 15 | case 'baseValues': 16 | calculateBaseValues(e.data.id, e.data.settings); 17 | break; 18 | default: 19 | console.error("unexpected message: %O", e.data); 20 | } 21 | } catch (ex) { 22 | console.error(ex); 23 | self.postMessage({ 24 | id: e.data.id, 25 | error: { 26 | error: ex.toString() 27 | } 28 | }) 29 | } 30 | }; 31 | 32 | function setupSim(settings) { 33 | var seed = Math.seed; 34 | if (typeof settings.seed === 'number') { 35 | seed = settings.seed; 36 | Math.seed = seed; 37 | } 38 | 39 | var crafterActions = []; 40 | 41 | for (var i = 0; i < settings.crafter.actions.length; i++) { 42 | crafterActions.push(AllActions[settings.crafter.actions[i]]); 43 | } 44 | 45 | var crafter = new Crafter(settings.recipe.cls, 46 | settings.crafter.level, 47 | settings.crafter.craftsmanship, 48 | settings.crafter.control, 49 | settings.crafter.cp, 50 | settings.crafter.specialist, 51 | crafterActions); 52 | var recipe = new Recipe(settings.recipe.baseLevel, settings.recipe.level, settings.recipe.difficulty, 53 | settings.recipe.durability, settings.recipe.startQuality, settings.recipe.maxQuality, 54 | settings.recipe.suggestedCraftsmanship, settings.recipe.suggestedControl); 55 | var synth = new Synth(crafter, recipe, settings.maxTricksUses, settings.reliabilityPercent / 100.0, 56 | settings.useConditions, 0); 57 | var synthNoConditions = new Synth(crafter, recipe, settings.maxTricksUses, settings.reliabilityPercent / 100.0, 58 | false, 0); 59 | 60 | var startState = NewStateFromSynth(synth); 61 | var startStateNoConditions = NewStateFromSynth(synthNoConditions); 62 | 63 | var sequence = []; 64 | 65 | for (var j = 0; j < settings.sequence.length; j++) { 66 | sequence.push(AllActions[settings.sequence[j]]); 67 | } 68 | return { 69 | seed: seed, 70 | synth: synth, 71 | startState: startState, 72 | startStateNoConditions: startStateNoConditions, 73 | sequence: sequence 74 | }; 75 | } 76 | 77 | function runProbablisticSim(id, settings) { 78 | var sim = setupSim(settings); 79 | 80 | var logOutput = new LogOutput(); 81 | 82 | logOutput.write('Use Conditions: %s\n\n'.sprintf(sim.synth.useConditions)); 83 | 84 | logOutput.write("Probabilistic Result\n"); 85 | logOutput.write("====================\n"); 86 | 87 | simSynth(sim.sequence, sim.startState, false, true, settings.debug, logOutput); 88 | 89 | self.postMessage({ 90 | id: id, 91 | success: { 92 | seed: sim.seed, 93 | sequence: settings.sequence, 94 | log: logOutput.log 95 | } 96 | }); 97 | } 98 | 99 | function runMonteCarloSim(id, settings) { 100 | var sim = setupSim(settings); 101 | 102 | var logOutput = new LogOutput(); 103 | 104 | logOutput.write('Seed: %d, Use Conditions: %s\n\n'.sprintf(sim.seed, sim.synth.useConditions)); 105 | 106 | var monteCarloSimHeader = "Monte Carlo Result of " + settings.maxMontecarloRuns + " runs"; 107 | logOutput.write(monteCarloSimHeader + "\n"); 108 | logOutput.write("=".repeat(monteCarloSimHeader.length)); 109 | logOutput.write("\n"); 110 | 111 | var mcSimResult = MonteCarloSim(sim.sequence, sim.synth, settings.maxMontecarloRuns, false, settings.conditionalActionHandling, false, settings.debug, logOutput); 112 | 113 | // Don't use conditions for final state to avoid oscillating results in the simulation state UI 114 | var states = MonteCarloSequence(sim.sequence, sim.startStateNoConditions, true, 'skipUnusable', false, false, logOutput); 115 | var finalState = states[states.length - 1]; 116 | 117 | var violations = finalState.checkViolations(); 118 | 119 | var result = { 120 | id: id, 121 | success: { 122 | seed: sim.seed, 123 | sequence: settings.sequence, 124 | log: logOutput.log, 125 | state: { 126 | quality: finalState.qualityState, 127 | durability: finalState.durabilityState, 128 | cp: finalState.cpState, 129 | progress: finalState.progressState, 130 | successPercent: mcSimResult.successPercent, 131 | hqPercent: hqPercentFromQuality(finalState.qualityState / settings.recipe.maxQuality * 100), 132 | feasible: violations.progressOk && violations.durabilityOk && violations.cpOk && violations.trickOk && violations.reliabilityOk, 133 | violations: violations, 134 | condition: finalState.condition, 135 | effects: finalState.effects, 136 | lastStep: finalState.lastStep, 137 | bonusMaxCp: finalState.bonusMaxCp 138 | } 139 | } 140 | }; 141 | 142 | self.postMessage(result); 143 | } 144 | 145 | function calculateBaseValues(id, settings) { 146 | var sim = setupSim(settings); 147 | 148 | var effCrafterLevel = getEffectiveCrafterLevel(sim.synth); 149 | var levelDifference = effCrafterLevel - sim.synth.recipe.level; 150 | var baseProgress = Math.floor(sim.synth.calculateBaseProgressIncrease(levelDifference, sim.synth.crafter.craftsmanship)); 151 | var baseQuality = Math.floor(sim.synth.calculateBaseQualityIncrease(levelDifference, sim.synth.crafter.control)); 152 | 153 | self.postMessage({ 154 | id: id, 155 | success: { 156 | baseValues: { 157 | progress: baseProgress, 158 | quality: baseQuality, 159 | }, 160 | }, 161 | }); 162 | } 163 | 164 | -------------------------------------------------------------------------------- /app/js/solver/eacomplex.js: -------------------------------------------------------------------------------- 1 | //noinspection ThisExpressionReferencesGlobalObjectJS 2 | if (this.ALGORITHMS === undefined) ALGORITHMS = {}; 3 | 4 | ALGORITHMS['eaComplex'] = { 5 | setup: function (population, toolbox, hof) { 6 | // initialize functions 7 | toolbox.register("mate", yagal_tools.cxRandomSubSeq, 3); 8 | toolbox.register("mutate1", yagal_tools.mutRandomSubSeq, 3, toolbox.randomActionSeq); 9 | toolbox.register("mutate2", yagal_tools.mutSwap); 10 | toolbox.register("mutate", yagal_tools.randomMutation, [toolbox.mutate1, toolbox.mutate2]); 11 | toolbox.register("selectParents", yagal_tools.selTournament, 7); 12 | toolbox.register("selectOffspring", yagal_tools.selBest); 13 | toolbox.register("selectSurvivors", yagal_tools.selBest); 14 | 15 | // evaluate fitness of starting population 16 | var fitnessesValues = toolbox.map(toolbox.evaluate, population); 17 | for (var i = 0; i < population.length; i++) { 18 | population[i].fitness.setValues(fitnessesValues[i]); 19 | } 20 | 21 | if (hof !== undefined) { 22 | hof.update(population); 23 | } 24 | }, 25 | gen: function (population, toolbox, hof) { 26 | // select parents 27 | var parents = toolbox.selectParents(population.length / 2, population); 28 | 29 | // breed offspring 30 | var offspring = yagal_algorithms.varAnd(parents, toolbox, 0.5, 0.2); 31 | 32 | function isFitnessInvalid(ind) { 33 | return !ind.fitness.valid(); 34 | } 35 | 36 | // evaluate offspring with invalid fitness 37 | var invalidInd = offspring.filter(isFitnessInvalid); 38 | var fitnessesValues = toolbox.map(toolbox.evaluate, invalidInd); 39 | for (var j = 0; j < invalidInd.length; j++) { 40 | invalidInd[j].fitness.setValues(fitnessesValues[j]); 41 | } 42 | 43 | // select offspring 44 | offspring = toolbox.selectOffspring(offspring.length / 2, offspring); 45 | 46 | // select survivors 47 | var survivors = toolbox.selectSurvivors(population.length - offspring.length, population); 48 | 49 | var nextPop = offspring.concat(survivors); 50 | 51 | if (hof !== undefined) { 52 | hof.update(nextPop); 53 | } 54 | 55 | return nextPop; 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /app/js/solver/easimple.js: -------------------------------------------------------------------------------- 1 | //noinspection ThisExpressionReferencesGlobalObjectJS 2 | if (this.ALGORITHMS === undefined) ALGORITHMS = {}; 3 | 4 | ALGORITHMS['eaSimple'] = { 5 | setup: function eaSimple_setup(population, toolbox, hof) { 6 | // initialize functions 7 | toolbox.register("mate", yagal_tools.cxRandomSubSeq, 3); 8 | toolbox.register("mutate", yagal_tools.mutRandomSubSeq, 3, toolbox.randomActionSeq); 9 | toolbox.register("select", yagal_tools.selTournament, 7); 10 | 11 | // evaluate fitness of starting population 12 | var fitnessesValues = toolbox.map(toolbox.evaluate, population); 13 | for (var i = 0; i < population.length; i++) { 14 | population[i].fitness.setValues(fitnessesValues[i]); 15 | } 16 | 17 | if (hof !== undefined) { 18 | hof.update(population); 19 | } 20 | }, 21 | gen: function eaSimple_gen(population, toolbox, hof) { 22 | var offspring = toolbox.select(population.length, population); 23 | 24 | offspring = yagal_algorithms.varAnd(offspring, toolbox, 0.5, 0.2); 25 | 26 | function isFitnessInvalid(ind) { 27 | return !ind.fitness.valid(); 28 | } 29 | 30 | // evaluate individuals with invalid fitness 31 | var invalidInd = offspring.filter(isFitnessInvalid); 32 | var fitnessesValues = toolbox.map(toolbox.evaluate, invalidInd); 33 | for (var j = 0; j < invalidInd.length; j++) { 34 | invalidInd[j].fitness.setValues(fitnessesValues[j]); 35 | } 36 | 37 | if (hof !== undefined) { 38 | hof.update(offspring); 39 | } 40 | 41 | return offspring; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /app/js/solver/service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ffxivCraftOptWeb.services.solver', []) 6 | .service('_solver', SolverService); 7 | 8 | function SolverService($timeout) { 9 | this.$timeout = $timeout; 10 | 11 | this.worker = new Worker('js/solver/worker.js'); 12 | 13 | var self = this, 14 | worker = this.worker; 15 | 16 | worker.onmessage = function (e) { 17 | if (e.data.progress) { 18 | self.$timeout(function () { 19 | self.callbacks.progress(e.data.progress); 20 | }); 21 | if (!self.stopRequested && e.data.progress.generationsCompleted < e.data.progress.maxGenerations) { 22 | worker.postMessage('rungen'); 23 | } 24 | else { 25 | worker.postMessage('finish'); 26 | } 27 | } 28 | else if (e.data.success) { 29 | self.$timeout(function () { 30 | self.callbacks.success(e.data.success); 31 | }); 32 | } 33 | else if (e.data.error) { 34 | self.$timeout(function () { 35 | self.callbacks.error(e.data.error); 36 | }); 37 | } 38 | else { 39 | console.error('unexpected message from solver worker: %O', e.data); 40 | self.$timeout(function () { 41 | self.callbacks.error({log: '', error: 'unexpected message from solver worker: ' + e.data}); 42 | }); 43 | } 44 | }; 45 | } 46 | 47 | SolverService.$inject = ['$timeout']; 48 | 49 | SolverService.prototype.start = function(settings, progress, success, error) { 50 | if (settings.recipe.startQuality === undefined) { 51 | settings.recipe = angular.copy(settings.recipe); 52 | settings.recipe.startQuality = 0; 53 | } 54 | 55 | this.stopRequested = false; 56 | this.callbacks = { 57 | progress: progress, 58 | success: success, 59 | error: error 60 | }; 61 | this.worker.postMessage({start: settings}); 62 | }; 63 | 64 | SolverService.prototype.stop = function() { 65 | this.stopRequested = true; 66 | }; 67 | 68 | SolverService.prototype.resume = function() { 69 | if (this.worker) { 70 | this.stopRequested = false; 71 | this.worker.postMessage('resume'); 72 | } 73 | }; 74 | })(); 75 | -------------------------------------------------------------------------------- /app/lib/lvl-drag-drop/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jason Turim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /app/lib/lvl-drag-drop/README.md: -------------------------------------------------------------------------------- 1 | #Native AngularJs drag and drop directive# 2 | 3 | An easy to use, native, directive to enable drag/drop in your angular app. This directive has no dependency on jQuery or other frameworks, it does require a browser that supports the HTML5 drag/drop events. 4 | 5 | [Live Demo](http://logicbomb.github.io/ng-directives/drag-drop.html) 6 | 7 | [Documentation](http://jasonturim.wordpress.com/2013/09/01/angularjs-drag-and-drop/) 8 | 9 | 10 | ##UUID Service## 11 | A very simple service for working with [UUIDs](http://en.wikipedia.org/wiki/Universally_unique_identifier). 12 | 13 | [Live Demo](http://logicbomb.github.io/ng-directives/uuid.html) 14 | 15 | [Documentation](http://jasonturim.wordpress.com/2013/09/01/angularjs-drag-and-drop/) 16 | 17 | ## dataTransfer hack 18 | 19 | The jQuery event object does not have a dataTransfer property... true, but one can try: 20 | 21 | 22 | jQuery.event.props.push('dataTransfer'); 23 | -------------------------------------------------------------------------------- /app/lib/lvl-drag-drop/lvl-drag-drop.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var module = angular.module("lvl.directives.dragdrop", ['lvl.services']); 5 | 6 | module.directive('lvlDraggable', [ 7 | '$rootScope', 'uuid', function ($rootScope, uuid) { 8 | return { 9 | restrict: 'A', 10 | link: function (scope, el, attrs, controller) { 11 | angular.element(el).attr("draggable", "true"); 12 | 13 | var id = angular.element(el).attr("id"); 14 | 15 | if (!id) { 16 | id = uuid.new() 17 | angular.element(el).attr("id", id); 18 | } 19 | el.bind("dragstart", function (e) { 20 | var oe = e.originalEvent || e; 21 | oe.dataTransfer.setData('text', id); 22 | $rootScope.$emit("LVL-DRAG-START"); 23 | }); 24 | 25 | el.bind("dragend", function (e) { 26 | $rootScope.$emit("LVL-DRAG-END"); 27 | }); 28 | } 29 | }; 30 | } 31 | ]); 32 | 33 | module.directive('lvlDropTarget', [ 34 | '$rootScope', 'uuid', function ($rootScope, uuid) { 35 | return { 36 | restrict: 'A', 37 | scope: { 38 | onDrop: '&' 39 | }, 40 | link: function (scope, el, attrs, controller) { 41 | var id = angular.element(el).attr("id"); 42 | if (!id) { 43 | id = uuid.new(); 44 | angular.element(el).attr("id", id); 45 | } 46 | 47 | el.bind("dragover", function (e) { 48 | if (e.preventDefault) { 49 | e.preventDefault(); // Necessary. Allows us to drop. 50 | } 51 | 52 | var oe = e.originalEvent || e; 53 | oe.dataTransfer.dropEffect = 'move'; // See the section on the DataTransfer object. 54 | return false; 55 | }); 56 | 57 | el.bind("dragenter", function (e) { 58 | // this / e.target is the current hover target. 59 | angular.element(e.target).addClass('lvl-over'); 60 | }); 61 | 62 | el.bind("dragleave", function (e) { 63 | angular.element(e.target).removeClass('lvl-over'); // this / e.target is previous target element. 64 | }); 65 | 66 | el.bind("drop", function (e) { 67 | if (e.preventDefault) { 68 | e.preventDefault(); // Necessary. Allows us to drop. 69 | } 70 | 71 | if (e.stopPropagation) { 72 | e.stopPropagation(); // Necessary. Allows us to drop. 73 | } 74 | var oe = e.originalEvent || e; 75 | var data = oe.dataTransfer.getData("text"); 76 | var dest = document.getElementById(id); 77 | var src = document.getElementById(data); 78 | 79 | scope.onDrop({dragEl: data, dropEl: id}); 80 | }); 81 | 82 | $rootScope.$on("LVL-DRAG-START", function () { 83 | var el = document.getElementById(id); 84 | angular.element(el).addClass("lvl-target"); 85 | }); 86 | 87 | $rootScope.$on("LVL-DRAG-END", function () { 88 | var el = document.getElementById(id); 89 | angular.element(el).removeClass("lvl-target"); 90 | angular.element(el).removeClass("lvl-over"); 91 | }); 92 | } 93 | }; 94 | } 95 | ]); 96 | 97 | })(); 98 | -------------------------------------------------------------------------------- /app/lib/lvl-drag-drop/lvl-uuid.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var module; 5 | 6 | try { 7 | module = angular.module('lvl.services'); 8 | } catch (e) { 9 | module = angular.module('lvl.services', []); 10 | } 11 | 12 | module.factory('uuid', function () { 13 | var svc = { 14 | new: function () { 15 | function _p8(s) { 16 | var p = (Math.random().toString(16) + "000000000").substr(2, 8); 17 | return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p; 18 | } 19 | 20 | return _p8() + _p8(true) + _p8(true) + _p8(); 21 | }, 22 | 23 | empty: function () { 24 | return '00000000-0000-0000-0000-000000000000'; 25 | } 26 | }; 27 | 28 | return svc; 29 | }); 30 | })(); 31 | -------------------------------------------------------------------------------- /app/lib/yagal/algorithms.js: -------------------------------------------------------------------------------- 1 | var yagal_algorithms = (function() { 2 | function isFitnessInvalid(ind) { 3 | return !ind.fitness.valid(); 4 | } 5 | 6 | function varCrossover(population, toolbox, cxpb) { 7 | var offspring = toolbox.map(toolbox.clone, population); 8 | 9 | for (var i = 1; i < offspring.length; i += 2) { 10 | if (Math.random() < cxpb) { 11 | var mated = toolbox.mate(offspring[i - 1], offspring[i]); 12 | mated[0].fitness.clearValues(); 13 | mated[1].fitness.clearValues(); 14 | offspring[i - 1] = mated[0]; 15 | offspring[i] = mated[1]; 16 | } 17 | } 18 | return offspring; 19 | } 20 | 21 | function varMutate(offspring, toolbox, mutpb) { 22 | for (var j = 0; j < offspring.length; j++) { 23 | if (Math.random() < mutpb) { 24 | var mutated = toolbox.mutate(offspring[j]); 25 | mutated[0].fitness.clearValues(); 26 | offspring[j] = mutated[0]; 27 | } 28 | } 29 | } 30 | 31 | function varAnd(population, toolbox, cxpb, mutpb) { 32 | var offspring = varCrossover(population, toolbox, cxpb); 33 | varMutate(offspring, toolbox, mutpb); 34 | 35 | return offspring; 36 | } 37 | 38 | return { 39 | varAnd: varAnd, 40 | varCrossover: varCrossover, 41 | varMutate: varMutate 42 | }; 43 | }()); 44 | -------------------------------------------------------------------------------- /app/lib/yagal/creator.js: -------------------------------------------------------------------------------- 1 | var yagal_creator = (function() { 2 | function Creator() { 3 | } 4 | 5 | Creator.prototype.create = function(name, base, attrs) { 6 | var ctor = function() { 7 | var obj = base.apply(this, arguments); 8 | 9 | obj = typeof obj === 'object' ? obj : this; 10 | 11 | if (attrs !== undefined) { 12 | for (var attr in attrs) { 13 | var x = attrs[attr]; 14 | if (typeof x === 'function') { 15 | /* jshint -W055 */ 16 | x = new x(); 17 | } 18 | obj[attr] = x; 19 | } 20 | } 21 | 22 | return obj; 23 | }; 24 | 25 | ctor.prototype = Object.create(base.prototype); 26 | ctor.prototype.constructor = ctor; 27 | 28 | this[name] = ctor; 29 | }; 30 | 31 | return { 32 | Creator: Creator 33 | }; 34 | }()); 35 | -------------------------------------------------------------------------------- /app/lib/yagal/fitness.js: -------------------------------------------------------------------------------- 1 | var yagal_fitness = (function() { 2 | function Fitness(values) { 3 | if (values !== undefined) { 4 | this.setValues(values); 5 | } 6 | } 7 | 8 | Fitness.prototype.weights = function() { 9 | return this._weights; 10 | }; 11 | 12 | Fitness.prototype.weightedValues = function() { 13 | return this._weightedValues; 14 | }; 15 | 16 | Fitness.prototype.setValues = function(values) { 17 | if (this._weights === undefined) { 18 | throw 'Fitness class has no weights defined; use defineFitnessClass([weights...])'; 19 | } 20 | 21 | var weighted = values.slice(); 22 | 23 | for (var i = 0; i < weighted.length; i++) { 24 | weighted[i] = weighted[i] * this._weights[i]; 25 | } 26 | 27 | this._weightedValues = weighted; 28 | 29 | return this; 30 | }; 31 | 32 | Fitness.prototype.clearValues = function() { 33 | delete this._weightedValues; 34 | }; 35 | 36 | Fitness.prototype.values = function() { 37 | if (this._weights === undefined) { 38 | throw 'Fitness class has no weights defined'; 39 | } 40 | 41 | if (this._weightedValues === undefined) { 42 | return undefined; 43 | } 44 | 45 | var unweighted = this._weightedValues.slice(); 46 | 47 | for (var i = 0; i < unweighted.length; i++) { 48 | unweighted[i] = unweighted[i] / this._weights[i]; 49 | } 50 | 51 | return unweighted; 52 | }; 53 | 54 | Fitness.prototype.compare = function(other) { 55 | if (!this.valid()) { 56 | return -1; 57 | } 58 | else if (!other.valid()) { 59 | return 1; 60 | } 61 | 62 | if (this._weightedValues.length !== other._weightedValues.length) { 63 | throw 'Cannot compare Fitnesses with differing lengths'; 64 | } 65 | 66 | for (var i = 0; i < this._weightedValues.length; i++) { 67 | if (this._weightedValues[i] < other._weightedValues[i]) { 68 | return -1; 69 | } 70 | else if (this._weightedValues[i] > other._weightedValues[i]) { 71 | return 1; 72 | } 73 | } 74 | 75 | return 0; 76 | }; 77 | 78 | Fitness.prototype.eq = function(other) { 79 | return this.compare(other) === 0; 80 | }; 81 | 82 | Fitness.prototype.lt = function(other) { 83 | return this.compare(other) < 0; 84 | }; 85 | 86 | Fitness.prototype.gt = function(other) { 87 | return this.compare(other) > 0; 88 | }; 89 | 90 | Fitness.prototype.lte = function(other) { 91 | return this.compare(other) <= 0; 92 | }; 93 | 94 | Fitness.prototype.gte = function(other) { 95 | return this.compare(other) >= 0; 96 | }; 97 | 98 | Fitness.prototype.valid = function() { 99 | if (this._weightedValues === undefined) { 100 | return false; 101 | } 102 | if (this._weightedValues.length !== this._weights.length) { 103 | return false; 104 | } 105 | for (var i = 0; i < this._weightedValues.length; i++) { 106 | if (isNaN(this._weightedValues[i])) { 107 | return false; 108 | } 109 | } 110 | return true; 111 | }; 112 | 113 | function defineFitnessClass(weights) { 114 | var ctor = function(values) { 115 | Fitness.call(this, values); 116 | }; 117 | 118 | ctor.prototype = Object.create(Fitness.prototype); 119 | ctor.prototype.constructor = ctor; 120 | ctor.prototype._weights = weights; 121 | 122 | return ctor; 123 | } 124 | 125 | return { 126 | defineFitnessClass: defineFitnessClass 127 | }; 128 | }()); 129 | -------------------------------------------------------------------------------- /app/lib/yagal/toolbox.js: -------------------------------------------------------------------------------- 1 | var yagal_toolbox = (function() { 2 | function _typeof(x) { 3 | if (Array.isArray(x)) { 4 | return 'array'; 5 | } 6 | else { 7 | return typeof x; 8 | } 9 | } 10 | 11 | function clone(x) { 12 | var seen = {}; 13 | function _clone(x) { 14 | if (x === null) { 15 | return null; 16 | } 17 | for (var s in seen) { 18 | if (s === x) { 19 | return seen[s]; 20 | } 21 | } 22 | switch(_typeof(x)) { 23 | case 'object': 24 | var newObject = Object.create(Object.getPrototypeOf(x)); 25 | seen[x] = newObject; 26 | for (var p in x) { 27 | newObject[p] = _clone(x[p]); 28 | } 29 | return newObject; 30 | case 'array': 31 | var newArray = []; 32 | seen[x] = newArray; 33 | for (var pp in x) { 34 | newArray[pp] = _clone(x[pp]); 35 | } 36 | return newArray; 37 | case 'number': 38 | return x; 39 | case 'string': 40 | return x; 41 | case 'boolean': 42 | return x; 43 | default: 44 | return x; 45 | } 46 | } 47 | return _clone(x); 48 | } 49 | 50 | function map(fn, arr) { 51 | return arr.map(fn); 52 | } 53 | 54 | function Toolbox() { 55 | this.clone = clone; 56 | this.map = map; 57 | return this; 58 | } 59 | 60 | Toolbox.prototype.register = function(name, fn) { 61 | var args = Array.prototype.slice.call(arguments, 2); 62 | this[name] = function() { 63 | var finalArgs = args.concat(Array.prototype.slice.call(arguments)); 64 | return fn.apply(null, finalArgs); 65 | }; 66 | }; 67 | 68 | Toolbox.prototype.unregister = function(name) { 69 | delete this[name]; 70 | }; 71 | 72 | return { 73 | Toolbox: Toolbox 74 | }; 75 | }()); 76 | -------------------------------------------------------------------------------- /app/modals/charimport.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /app/modals/macroimport.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | 18 | 22 | -------------------------------------------------------------------------------- /app/modals/settingsimport.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | -------------------------------------------------------------------------------- /app/views/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

{{ 'ABOUT_INTRO' | translate }}

4 | 5 |

{{ 'ABOUT_INFORMATION' | translate}}

6 | 7 | 12 | 13 |

{{ 'ABOUT_BUG' | translate }}

14 | 15 |

{{ 'ABOUT_REPORT_ISSUES' | translate }}{{ 'ABOUT_REPORT_HERE' | translate }} 16 | 17 |

{{ 'ABOUT_CONTRIBUTORS' | translate}}

18 | 19 |

{{ 'ABOUT_CONTRIBUTORS_BLURB' | translate }}

20 | 21 |
    22 |
  • Ermad
  • 23 |
  • gr3ger
  • 24 |
  • ShammyLevva
  • 25 |
  • RyuaNerin
  • 26 |
  • kongspark
  • 27 |
  • Jengines
  • 28 |
  • yls888
  • 29 |
  • kurax
  • 30 |
  • zzoru
  • 31 |
  • tyrone-sudeium
  • 32 |
  • solnus
  • 33 |
  • nmarquesb
  • 34 |
  • butterflo
  • 35 |
  • Igor-Yavych
  • 36 |
37 | 38 |

{{ 'ABOUT_ANYTHING' | translate }}

39 | 40 |

{{ 'ABOUT_RESEMBLANCE' | translate }}Crafting as a Service{{ 'ABOUT_COINCIDENCE' | translate }}

41 | -------------------------------------------------------------------------------- /app/views/crafter-attributes.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |   {{tab.name | translate}} 6 | 7 | 16 | 17 |
18 |
19 |
20 |
21 | {{ 'ATTRIBUTES' | translate }} 22 |
23 | 24 |
25 | 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 | 38 |
39 |
40 |
41 | 42 |
43 | 44 |
45 |
46 |
47 | 48 |
49 | 50 |

{{ 'SPECIALIST_DESC' | translate }}

51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {{ 'AVAILABLE_ACTIONS' | translate }} 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
69 |
70 |
71 |
72 |
73 |
74 | -------------------------------------------------------------------------------- /app/views/instructions.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
    5 |
  1. {{ 'INSTRUCTIONS_STEP_1' | translate }}{{ 'NAV_CRAFTER_ATTRIBUTES' | translate }}{{ 'SECTION' | translate }}
  2. 6 | 7 |
  3. {{ 'INSTRUCTIONS_STEP_2' | translate }}
  4. 8 |
  5. {{ 'INSTRUCTIONS_STEP_3' | translate }}
  6. 9 |
  7. {{ 'INSTRUCTIONS_STEP_4' | translate }}{{ 'AVAILABLE_ACTIONS' | translate }}{{ 'SECTION' | translate }}
  8. 10 |
  9. {{ 'INSTRUCTIONS_STEP_5' | translate }}
  10. 11 |
  11. {{ 'INSTRUCTIONS_STEP_6' | translate }}
  12. 12 |
  13. {{ 'INSTRUCTIONS_STEP_7' | translate }}{{ 'NAV_SIMULATOR' | translate }}{{ 'SECTION' | translate }}
  14. 13 |
  15. {{ 'INSTRUCTIONS_STEP_8' | translate }}
  16. 14 |
  17. {{ 'INSTRUCTIONS_STEP_9' | translate }}
  18. 15 |
  19. {{ 'INSTRUCTIONS_STEP_10' | translate }}
  20. 16 |
  21. {{ 'INSTRUCTIONS_STEP_11' | translate }}
  22. 17 |
  23. {{ 'INSTRUCTIONS_STEP_12' | translate }}
  24. 18 |
  25. {{ 'INSTRUCTIONS_STEP_13' | translate }}{{ 'NAV_SIMULATOR' | translate }}{{ 'SECTION' | translate }}
  26. 19 |
  27. {{ 'INSTRUCTIONS_STEP_14' | translate }}
  28. 20 |
  29. {{ 'INSTRUCTIONS_STEP_15' | translate }}{{ 'SAVE' | translate }}.
  30. 21 |
  31. {{ 'INSTRUCTIONS_STEP_16' | translate }}{{ 'SOLVE' | translate }}
  32. 22 |
  33. {{ 'INSTRUCTIONS_STEP_17' | translate }}{{ 'OPTIONS' | translate }}{{ 'SECTION' | translate }}
  34. 23 |
  35. {{ 'INSTRUCTIONS_STEP_18' | translate }}{{ 'MONTE_CARLO_SIM' | translate }}/{{ 'PROBABILISTIC_SIM' | translate }}{{ 'SECTION' | translate }}
  36. 24 |
  37. {{ 'INSTRUCTIONS_STEP_19' | translate }}
    {{ 'ABBREVIATIONS' | translate }} 25 |
      26 |
    • {{ 'DUR_ABBREVIATION' | translate }}
    • 27 |
    • {{ 'CP_ABBREVIATION' | translate }}
    • 28 |
    • {{ 'EQUA_ABBREVIATION' | translate }}
    • 29 |
    • {{ 'EPRG_ABBREVIATION' | translate }}
    • 30 |
    • {{ 'WAC_ABBREVIATION' | translate }}
    • 31 |
    32 |
  38. 33 |
  39. {{ 'INSTRUCTIONS_STEP_20' | translate }}
  40. 34 |
  41. {{ 'INSTRUCTIONS_STEP_21' | translate }}
  42. 35 |
36 |
37 | 38 |
39 | 40 | 41 |

{{ 'USING_SOLVER_INFO' | translate }} 42 | 43 |

    44 |
  1. {{ 'USING_SOLVER_STEP_1' | translate }}
  2. 45 |
  3. {{ 'USING_SOLVER_STEP_2' | translate }}
  4. 46 |
  5. {{ 'USING_SOLVER_STEP_3' | translate }}
  6. 47 |
  7. {{ 'USING_SOLVER_STEP_4' | translate }}
  8. 48 |
  9. {{ 'USING_SOLVER_STEP_5' | translate }}
  10. 49 |
50 |
51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Gordon Tyler", 3 | "name": "ffxiv-craft-opt-web", 4 | "description": "FFXIV Crafting Optimizer Website", 5 | "version": "0.0.1", 6 | "homepage": "http://ffxiv.lokyst.net", 7 | "repository": { 8 | "type": "git", 9 | "url": "git@github.com:doxxx/ffxiv-craft-opt-web.git" 10 | }, 11 | "engines": { 12 | "node": "*" 13 | }, 14 | "dependencies": { 15 | "connect": "^3.4.0", 16 | "morgan": "^1.6.1", 17 | "serve-static": "^1.10.0" 18 | }, 19 | "devDependencies": { 20 | "browser-sync": "^2.23.6" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/export_settings.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var settings = {}; 5 | 6 | var keys = [ 7 | 'NG_TRANSLATE_LANG_KEY', 8 | 'crafterStats', 9 | 'pageStage_v2', 10 | 'synths' 11 | ]; 12 | for (var i = 0; i < keys.length; i++) { 13 | var key = keys[i]; 14 | settings[key] = localStorage[key]; 15 | } 16 | 17 | window.prompt('Copy the text below to the clipboard', JSON.stringify(settings)); 18 | })(); 19 | -------------------------------------------------------------------------------- /scripts/extract_recipes.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sqlite3 3 | import json 4 | import itertools 5 | import operator 6 | 7 | conn = sqlite3.connect('app_data.sqlite') 8 | 9 | classes = {key: name for (key, name) in conn.execute('select Key, Name_en from ClassJob')} 10 | craftTypes = {key: classJob for (key, classJob) in conn.execute('select Key, ClassJob from CraftType')} 11 | itemNames = {key: name for (key, name) in conn.execute('select Key, UIName_en from Item')} 12 | 13 | def makeRecipe(craftType, itemId, level, data): 14 | data = json.loads(data) 15 | r = { 16 | 'cls': classes[craftTypes[craftType]], 17 | 'name': itemNames[itemId], 18 | 'level': int(level), 19 | 'durability': int(data['material_point']), 20 | 'difficulty': int(data['work_max']), 21 | 'maxQuality': int(data['quality_max']) 22 | } 23 | return r 24 | 25 | recipes = [makeRecipe(craftType, itemId, level, data) 26 | for (craftType, itemId, level, data) 27 | in conn.execute('select CraftType, CraftItemId, Level, data from Recipe')] 28 | 29 | recipeClassName = operator.itemgetter('cls') 30 | recipes.sort(key=recipeClassName) 31 | recipesByClass = {k:list(v) for k,v in itertools.groupby(recipes, recipeClassName)} 32 | recipeLevel = operator.itemgetter('level') 33 | for cls in recipesByClass: 34 | clsRecipes = recipesByClass[cls] 35 | clsRecipes.sort(key=recipeLevel) 36 | for recipe in clsRecipes: 37 | del recipe['cls'] 38 | 39 | recipeDbFile = open('recipedb.js', 'w') 40 | try: 41 | recipeDbFile.write('FFXIV_Recipe_DB = ') 42 | json.dump(recipesByClass, recipeDbFile) 43 | finally: 44 | recipeDbFile.close() 45 | -------------------------------------------------------------------------------- /scripts/import_settings.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var json = window.prompt('Paste the copied text here') 5 | if (json) { 6 | var data; 7 | try { 8 | data = JSON.parse(json); 9 | } 10 | catch (err) { 11 | window.alert('The text you pasted is incorrect, please try again.\n\n' + err.message); 12 | return; 13 | } 14 | console.log('Importing settings into local storage:', data); 15 | for (var key in data) { 16 | if (data.hasOwnProperty(key)) { 17 | localStorage[key] = data[key]; 18 | } 19 | } 20 | window.alert('Settings have been imported, application will now be reloaded'); 21 | window.location.reload(); 22 | } 23 | })(); 24 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var http = require('http'); 4 | var connect = require('connect'); 5 | var serveStatic = require('serve-static'); 6 | var morgan = require('morgan'); 7 | var path = require('path'); 8 | 9 | var app = connect(); 10 | 11 | app.use(morgan('dev')); 12 | app.use('/', serveStatic(path.join(__dirname, 'app'))); 13 | 14 | const port = 8001; 15 | app.listen(port); 16 | 17 | console.log('Server listening on port', port); 18 | -------------------------------------------------------------------------------- /start-browser-sync.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | browser-sync start --server app --files app/** 3 | -------------------------------------------------------------------------------- /webserver.cmd: -------------------------------------------------------------------------------- 1 | npm start 2 | --------------------------------------------------------------------------------