├── .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 |
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 | {{ 'PROGRESS' | translate }} (100% {{ 'EFFICIENCY' | translate }} = {{baseValues.progress}}) |
11 |
12 |
13 |
14 |
15 | |
16 | {{progress}} / {{recipe.difficulty}} |
17 |
18 |
19 | {{ 'QUALITY' | translate }} (100% {{ 'EFFICIENCY' | translate }} = {{baseValues.quality}}) |
20 |
21 |
22 |
23 |
24 | |
25 | {{quality}} / {{recipe.maxQuality}} |
26 |
27 |
28 | {{ 'CP' | translate }} |
29 |
30 |
31 |
32 |
33 | |
34 | {{cp}} / {{maxCp}} |
35 |
36 |
37 |
38 |
39 |
40 |
41 | {{ 'DURABILITY' | translate }} {{durability}} / {{recipe.durability}}
42 | |
43 |
44 | {{ 'HQ' | translate }} {{hqPercent}}%
45 | |
46 |
47 | {{ 'RELIABILITY' | translate }} {{successPercent | number: 0}}%
48 | |
49 |
50 |
51 |
52 |
53 |
54 |
55 | {{ 'ATTRIBUTES' | translate}}
56 |
57 |
58 |
59 |
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 |
--------------------------------------------------------------------------------
/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 |
6 |
7 |
13 |
14 |
{{error}}
15 |
16 |
17 |
18 |
22 |
23 |
24 | {{result.name}}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
No data available!
32 |
33 |
34 |
35 |
36 | |
37 |
38 |
39 | |
40 |
41 |
42 | Level |
43 | {{classInfo.level}} |
44 |
45 |
46 | Craftsmanship |
47 | {{classInfo.craftsmanship}} |
48 |
49 |
50 | Control |
51 | {{classInfo.control}} |
52 |
53 |
54 | CP |
55 | {{classInfo.cp}} |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
![]()
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/modals/macroimport.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
17 |
18 |
22 |
--------------------------------------------------------------------------------
/app/modals/settingsimport.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
{{ 'SETTINGS_EXPORT' | translate }}
11 |
14 |
{{ 'SETTINGS_DOWNLOAD_INFO' | translate }}
15 |
16 |
17 |
{{ 'SETTINGS_IMPORT' | translate }}
18 |
{{ 'SETTINGS_DROP_FILES' | translate }}
19 |
{{ 'SETTINGS_IMPORT_INFO' | translate }}
20 |
21 |
22 |
23 |
24 |
25 |
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 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/views/instructions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - {{ 'INSTRUCTIONS_STEP_1' | translate }}{{ 'NAV_CRAFTER_ATTRIBUTES' | translate }}{{ 'SECTION' | translate }}

6 |
7 | - {{ 'INSTRUCTIONS_STEP_2' | translate }}
8 | - {{ 'INSTRUCTIONS_STEP_3' | translate }}
9 | - {{ 'INSTRUCTIONS_STEP_4' | translate }}{{ 'AVAILABLE_ACTIONS' | translate }}{{ 'SECTION' | translate }}

10 | - {{ 'INSTRUCTIONS_STEP_5' | translate }}
11 | - {{ 'INSTRUCTIONS_STEP_6' | translate }}
12 | - {{ 'INSTRUCTIONS_STEP_7' | translate }}{{ 'NAV_SIMULATOR' | translate }}{{ 'SECTION' | translate }}

13 | - {{ 'INSTRUCTIONS_STEP_8' | translate }}
14 | - {{ 'INSTRUCTIONS_STEP_9' | translate }}
15 | - {{ 'INSTRUCTIONS_STEP_10' | translate }}
16 | - {{ 'INSTRUCTIONS_STEP_11' | translate }}
17 | - {{ 'INSTRUCTIONS_STEP_12' | translate }}
18 | - {{ 'INSTRUCTIONS_STEP_13' | translate }}{{ 'NAV_SIMULATOR' | translate }}{{ 'SECTION' | translate }}

19 | - {{ 'INSTRUCTIONS_STEP_14' | translate }}

20 | - {{ 'INSTRUCTIONS_STEP_15' | translate }}{{ 'SAVE' | translate }}.
21 | - {{ 'INSTRUCTIONS_STEP_16' | translate }}{{ 'SOLVE' | translate }}
22 | - {{ 'INSTRUCTIONS_STEP_17' | translate }}{{ 'OPTIONS' | translate }}{{ 'SECTION' | translate }}
23 | - {{ 'INSTRUCTIONS_STEP_18' | translate }}{{ 'MONTE_CARLO_SIM' | translate }}/{{ 'PROBABILISTIC_SIM' | translate }}{{ 'SECTION' | translate }}

24 | - {{ '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 |
33 | - {{ 'INSTRUCTIONS_STEP_20' | translate }}
34 | - {{ 'INSTRUCTIONS_STEP_21' | translate }}
35 |
36 |
37 |
38 |
39 |
40 |
41 | {{ 'USING_SOLVER_INFO' | translate }}
42 |
43 |
44 | - {{ 'USING_SOLVER_STEP_1' | translate }}
45 | - {{ 'USING_SOLVER_STEP_2' | translate }}
46 | - {{ 'USING_SOLVER_STEP_3' | translate }}
47 | - {{ 'USING_SOLVER_STEP_4' | translate }}
48 | - {{ 'USING_SOLVER_STEP_5' | translate }}
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 |
--------------------------------------------------------------------------------