Big clean up of structure and contents

This commit is contained in:
Josh Freeman
2018-10-03 18:24:44 +01:00
parent 74795df42b
commit 7d434e502b
25 changed files with 185 additions and 140 deletions
+34
View File
@@ -0,0 +1,34 @@
# Achievements Requirement Types
Achievements link to a "pre-requisite type" that defines it as completed.
**This is a known list of types as of: 3rd October, 2018**
```
0: complete specific Legacy thing
1: do n things
2: complete n other achievements
3: achieve n levels as class
4: affix n materia to the same piece of gear
5: complete all n of these requirement_2 - 9 things
6: complete a specific quest
7: complete all specific hunting log entries
8: discover every location within...
9: complete any of these requirement_2 - 9 quests (?)
10: level your companion chocobo to rank n
11: achieve PVP rank n with a specific Grand Company
12: participate in n matches in The Feast
13: triumph in n matches in The Feast
14: complete a specific Trial
15: achieve rank n with a specific Beast Tribe
16: there is no type 16
17: participate in n Frontline matches
18: guide a specific Grand Company to n Frontline victories
19: triumph in n Frontline matches
20: attune to all aether currents in a specific area
21: obtain n minions
22: there is no type 22
23: complete all Verminion challenges
24: obtain a variety of anima weapon
25: finish top 10 in a Regional Feast Championship
```
+46
View File
@@ -0,0 +1,46 @@
# Collectible Reward Info
MasterpieceSupplyDuty is where all of the Collectable information is kept. Unknown how scrips are calculated for base value,
but Bonus / High Bonus is calculated very easily, for both EXP and Scrips.
(rounded down) Base * 1.1 = Bonus
(rounded down) Base * 1.2 = High Bonus
Items which are 'in demand' (a yellow star next to their name in the turn-in window) are multipled by 20% as well (*1.2),
so if an item is in demand then its base value is what the 'High Bonus' is normally, while Bonus/High Bonus are multiplied by
1.1 and 1.2!
Example:
Base Item: Level 57 Hardsilver Hatchet. 46 Blue Scrips, 119808 EXP.
Bonus Scrips Value (46*1.1) = 50.6 (round down to 50).
High Scrips Value (46*1.2) = 55.2 (round down to 55).
Bonus EXP Value (119808*1.1)= 131788.8 (rounded down to 131788).
High EXP Value (119808*1.2)= 143769.6 (rounded down to 143769).
If Hardsilver Hatchet were in demand, then the base value would change:
Scrips: 55
EXP : 143769
Bonus Scrips: (55*1.1 round down) = 60
High Scrips: (55*1.2 round down) = 66
Bonus EXP : (143769*1.1 round down) = 158145
High EXP : (143769*1.2 round down) = 172522
Collectability ranges are in the MasterpieceSupplyDuty file, so all that's needed is to figure out how scrips and exp are
actually calculated. EXP might be set in stone somewhere, as I've noticed all of the CRAFTING Collectables have the same EXP
rewards across all classes for the exact same level (assuming an item isn't in demand, of course. But if an item is in demand,
you can divide it by 1.2 and round up to get the info!) Gathering collectables I'd presume share a similar EXP systme, but I
haven't mapped out those values yet.
Level = EXP
------------------
Level 51 = 52920
Level 52 = 63360
Level 53 = 77760
Level 54 = 93600
Level 55 = 110880
Level 56 = 103680
Level 57 = 119808
Level 58 = 164505
Level 59 = 155520
Level 60 = 175564
+240
View File
@@ -0,0 +1,240 @@
# Companion App API
- https://eu.finalfantasyxiv.com/companion/
The FINAL FANTASY XIV companion app has the ability to query the game servers for a specific resource to pull information (market info, retainer items, player items, chat messages, etc). This document will list research and provide logic for obtaining data out of the app as well as quering the companion API.
What we know:
- The Companion App Queries a PHP 7 server.
- Queries are not performed in real-time but instead a request adds to a queue and then the servers process the request and feed the result back to the same request ID when queried again. This means you have to query several times to the same url (using the same request-id) in order to finally get a result on the nth try. This is usually around 2-3 seconds.
- Tokens last 24 hours before being expired. Unknown if using the companion app will "increase" the duration a token however using the API does not extend the token duration.
- You can download the APK anywhere and rename it to `.zip` to extract the contents, there are some nice icons and a very basic `sqlite` file, unfortunately this does not contain any useful info like Libra did (eg no dungeon loot tables anymore...)
## Current tools
Minoost has done some fantastic work reverse engineering the auth of the app, you can find his work here: https://github.com/Minoost/libpompom-sharp
## Getting data
This segment will provide a small amount of PHP as that is what I am familiar with, however because I've not translated the auth logic into PHP you will need a valid token
### Getting a token
This is quite easy if you setup some software. I use: https://www.telerik.com/fiddler
- I use my iPhone X, you do not need to jail break: https://docs.telerik.com/fiddler/Configure-Fiddler/Tasks/ConfigureForiOS
- If you need help with setting up Fiddle to talk to your iphone, just ping Vekien in xivapi.com Discord, it is very easy.
- If you have an android, it is probably even simplier, there will be lots of guides.
Once you have a proxy to fiddler, go to filters and add the url `companion-eu.finalfantasyxiv.com`, change `-eu` to your data center, or remove it completely to see login/auth queries. If you use the app from here you can begin to see the different endpoints and how they work.
Once you have a valid token, you can provide it to any endpoint and everything will work. The following script in PHP requires you to install guzzle `composer req guzzlehttp/guzzle` (you could just use curl internally if you prefer)
**Some header info:**
- `token` - Your token from the app, find via Fiddler
- `request-id` - Any kind of string can be here, the app uses UUID however it can be anything, a string, text, numbers, whatever, a poem...
> **Important** if you make the `request-id` unique PER REQUEST you will have a huge delay, around 2 seconds per call, if you use the same string for each request, you will get responses instantly from the API (often first query). It is more beneficial to hard code your request id, but this is not intentional by SE i believe, this is not what the app does...
> **Important update: 27th August 2018** - This "exploit" seems to have been patched, there looks to be `request-id` rate limiting, a very basic NGINX with bursting. Need more testing. The request-id can still be anything but if you use the same one you'll get rate-limited by the app.
- `Content-Type` - `application/json;charset=utf-8`
- `Accept` - `*/*'`
- `domain-type` - `global`
- `User-Agent` - `ffxivcomapp-e/1.0.1.0 CFNetwork/974.2.1 Darwin/18.0.0`
**PHP code to query the API**
```php
<?php
require __DIR__ .'/vendor/autoload.php';
use GuzzleHttp\Client;
$headers = [
// Your token, you would need to watch your traffic (eg I use Fiddler)
// for this or use: https://github.com/Minoost/libpompom-sharp
'token' => '<put your token here>',
'request-id' => "lol whatever you want here",
// other random blab
'Content-Type' => 'application/json;charset=utf-8',
'Accept' => '*/*',
'domain-type' => 'global',
'User-Agent' => 'ffxivcomapp-e/1.0.1.0 CFNetwork/974.2.1 Darwin/18.0.0',
];
$client = new Client([
'base_uri' => 'https://companion-eu.finalfantasyxiv.com/', //'https://companion.finalfantasyxiv.com',
'timeout' => 10,
]);
/**
* base_uri = https://companion.finalfantasyxiv.com
* /points/status
* /login/character
* /login/characters
* /login/region
*
* base_uri = https://companion-eu.finalfantasyxiv.com
* ^^ can be na or ja (i think data center?)
*
* These require the correct endpoint, eg:
* - login/characters/{character_id}, you need to query /login/character or /login/characters to
* get your character id.
*
* It returns a region, which will be the new 'base_uri'
*
* /character/login-status
* /items/character
* /market/items/catalog/18189 (<-- item id , aka "catalogId")
*/
// You have to keep hitting the API until you get a 200,
// as it queues requests and processes them in the background
// This will attempt 15 times delaying for 250ms
foreach (range(0, 15) as $i) {
// get market info for item id 18189
$response = $client->get('/sight-v060/sight/market/items/catalog/18189', [
\GuzzleHttp\RequestOptions::HEADERS => $headers,
]);
if ($response->getStatusCode() == 200) {
// this is the API JSON data
$data = (string)$response->getBody();
// this is the json response from the server, do something with it.
print_r($data);
break;
}
// delay for 250 milliseconds
usleep(250000);
}
```
To run this code:
- Update the `token`
- install dependencies using composer: `composer req guzzlehttp/guzzle`
- Copy the code to a file, eg: `companion.php`
- Call: `php companion.php`
- the variable `$data` will be json, you could do `json_decode($data, true)` to get the response as an array of data.
### Market Response info
- `stack` Quantity for sale
- `catalogId` ItemID you see in the game files
- `signatureName` the person who crafted the item
- `isCrafted` obvious
- `hq` hq or not, (see how SE don't do `isHq`, consistency!)
- `stain` the stain values, can't remember how this is formatted
- `sellPrice` obvious the gil price
- `sellRetainerName` the name of the retainer
- `town` the id of the town, this matches identically to Town.csv you get from the data files
- `materia` this is an array of materia:
- `key` = ID of the materia in the materia.csv file
- `grade` = the grade of the materia, which if you see in the materia.csv is just a numeric column number, starts from 0
### Minor notes
- Market endpoint ids match ItemSearchCategory.csv
- Items that are not sellable can be queried, you will get no listings however you will get lodestone ID's which can be useful (HQ Icons!)
- There doesn't seem to be any restrictions on spam or concurrent queries, I've been able to setup 4 terminals and query every item on the market board within 2 hours. I am going to test higher concurrent queries and see if there is a rate-limit, or I get banned lol.
- At this time if you are looking to query the market board for every server you would need to have an account per server this is due to how the app locks you into 1 character for an account, if you try to switch characters it would reset your token. You could in theory use 1 account for many servers however you could not do this in real time and due to the nature of the API taking 2-3 seconds a response, you're looking at about 8 hours to query every possible item for 1 server. I'll let someone else handle the logistics of this (*note: Not tested if a token is reset when switching character, just assuming by how its designed*)
- There are 66 servers, cheapest acc: $12.99 (trail accounts do not work), so looking at $858/month for 1 char per server :D
- No known way to query Korean or Chinese market boards at this time.
### All endpoints:
##### Payments
- POST("points/kupo-nuts")
- PUT("points/mog-coins/android")
- GET("purchase/charge")
- GET("purchase/cesa-limit")
- GET("purchase/user-birth")
- GET("points/history")
- GET("points/status")
- GET("points/products")
- POST("purchase/user-birth")
- POST("points/interrupted-process")
- POST("purchase/transaction")
##### Market
- DELETE("market/retainers/{cid}/rack/{itemId}")
- PATCH("market/retainers/{cid}/rack/{itemId}")
- GET("market/items/catalog/{catalogId}/hq")
- `catalogId` = ItemID in game files
- GET("market/items/catalog/{catalogId}")
- GET("market/items/category/{categoryId}")
- `categoryId` = ItemSearchCategory.csv in game files
- GET("market/retainers/{cid}")
- GET("market/items/history/catalog/{catalogId}")
- POST("market/item")
- POST("market/retainers/{cid}/rack")
- POST("market/retainers/{cid}")
- POST("market/payment/transaction")
- DELETE("market/retainers/{cid}")
##### Login
- DELETE("login/auth") - Headers({"domain-type: global"})
- GET("login/character")
- GET("login/characters") - Headers({"domain-type: global"})
- GET("login/region") - Headers({"domain-type: global"})
- POST("login/auth") - Headers({"domain-type: global"})
- POST("login/characters/{cid}") - Headers({"domain-type: global"})
- `{cid}` is an internal id and not what you see on lodestone
- POST("login/token") - Headers({"domain-type: global"})
- POST("login/advertising-id")
- POST("login/fcm-token")
##### Item
- GET("items/character")
- GET("character/login-status")
- GET("items/retainers/{retainerCid}")
- GET("retainers")
- PUT("items/{type}/{cid}/gil")
- PUT("items/{type}/{cid}/{storage}")
- DELETE("items/{type}/{cid}/{storage}/{itemId}")
- PUT("items/recycle/{itemId}")
##### ChatRoom
- POST("chatrooms/{rid}/members")
- DELETE("chatrooms/{rid}")
- DELETE("chatrooms/{rid}/messages/{seqNum}")
- GET("chatrooms/{rid}")
- GET("chatrooms")
- GET("chatrooms/{rid}/messages")
- POST("chatrooms")
- POST("chatrooms/{rid}/messages")
- POST("chatrooms/{rid}/push-notification")
- PATCH("chatrooms/{rid}/messages/last-chat")
- PATCH("chatrooms/{rid}/setting")
##### Address Book
- HTTP(hasBody=true, method="DELETE", path="address-book/blocklist")
- GET("address-book")
- GET("address-book/{cid}/profile")
- POST("address-book/blocklist")
##### Schedule
- PATCH("schedules/{sid}/cancel")
- PATCH("schedules/{sid}/close")
- DELETE("schedules/{sid}")
- PATCH("schedules/{sid}")
- GET("schedules/chatrooms/{rid}")
- GET("schedules/{sid}")
- GET("schedules/history")
- GET("schedules")
- POST("schedules")
- POST("schedules/{sid}/push-notification")
- PATCH("schedules/{sid}/role")
##### Report
- POST("report/chatrooms/{rid}/message")
- POST("report/chatrooms/{rid}")
- POST("report/schedules/{sid}")
- POST("report/schedules/{sid}/comment")
+93
View File
@@ -0,0 +1,93 @@
# Emote Log Messages
Attempting to debug the emote LogMessages
---
100 Standard Emotes 1 0 False
<Clickable(
<If(Equal(ObjectParameter(1),ObjectParameter(2)))>
you
<Else/>
<If(PlayerParameter(7))>
<SheetEn(ObjStr,2,PlayerParameter(7),1,1)/>
<Else/>
ObjectParameter(2)
</If>
</If>)/>
<If(Equal(ObjectParameter(1),ObjectParameter(2)))>
look
<Else/>
looks
</If>
at
<If(Equal(ObjectParameter(1),ObjectParameter(3)))>
<If(PlayerParameter(8))>
<SheetEn(ObjStr,2,PlayerParameter(8),1,1)/>
<Else/>
you
</If>
<Else/>
<If(PlayerParameter(8))>
<SheetEn(ObjStr,2,PlayerParameter(8),1,1)/>
<Else/>
ObjectParameter(3)
</If>
</If>
in surprise.
Un-targeted
Solo: [You] look surprised!
Someone else: [Siyukan Virtue] looks surprised!
Targeted
targeting other: [You] [look] at [the housing enthusiast] in surprise.
player targeting you: [Siyukan Virtue] [looks] at [you] in surprise.
player targeting other: [Siyukan Virtue] [looks] at [the housing enthusiast] in surprise.
JSON:
"en": [
"?? missing character choice ",
{
"condition": {
"left_operand": "target",
"operator": "==",
"right_operand": "not_self"
},
"no": "looks",
"yes": "look"
},
" at ",
{
"condition": {
"left_operand": "target",
"operator": "==",
"right_operand": "self"
},
"no": [
{
"condition": "is_player",
"no": "self",
"yes": "<SheetEn(ObjStr,2,PlayerParameter(8),1,1)/>"
}
],
"yes": [
{
"condition": "is_player",
"no": "you",
"yes": "<SheetEn(ObjStr,2,PlayerParameter(8),1,1)/>"
}
]
},
" in surprise."
],
+75
View File
@@ -0,0 +1,75 @@
# FATE Experience Points
Fate EXP is still a mystery, research so far:
Equation: ` fate offset 22 * paramgrow offset 8 + paramgrow offset 8 = exp`
# Where it has worked
**Example: Whiteout, level 36 fate, level 36 char, 18900 exp (9450 w/ no bonus)**
- Whiteout - fate id: `465`, offset `22` = `41`
- paramgrow (id `36` = your level??? or fate level...), offset `8` = `225 `
- Equation: `41 * 225 + 225 = 9450`
---
**Example:A Log Up, level 35 fate, level 35 char, 18040 exp (9020 w/ no bonus)***
- A Log Up - fate id: 466, offset `22` = `40`
- paramgrow (id `35` = your level??? or fate level...), offset `8` = `220 `
- Equation: `40 * 220 + 220 = 9020`
---
# Where it has not worked
**Eye in the Sky, level 50 fate, level 50 char. 32400 (+50% exp..) - 21600 without bonus**
- Eye in the Sky - fate id: 787, offset 22 = 54
- paramgrow (id 50), offset 8 = 300
Equation: `54 * 300 + 300 = 16500`
---
Baa Baa Black Sheep: id = 463
Offset 22 = 41
Offset 8 = 220 for paramgrow 36 (fate level).
41 * 225 + 225 = 9450
level 32 fate, level 31 mch (so an under leveled mch), 15016 exp (+100% armoury bonus) should be 7508 without
---
Jewels-of-the-Isle: id = 280
Complete level: 32
fate - offset 22 = 37
paramgrow - offset 8 = 200
37 * 200 + 200
---
Follow the Light, level 33 fate on a level 31 char. 14678 exp (+100%), should be 7339 without bonus
Fate ID: 286
fate - offset 22 = 38
paramgrow offset 8 = 200 (33 = 210)
---
<Hezkezl>: Just a Matter of Rut, level 31 FATE. Level 31 mch, got 15200 exp (7600 w/out bonus)
fate id: 287
fate - offset 22 = 36
paramgrow offset 8 = 200
36 * 200 + 200
+8
View File
@@ -0,0 +1,8 @@
# FATE Levels
The sync level for fates is always +5 of the fate level.
## Test Data
- level 9 fate, level 60 char syncs down to 14
- level 39 fate, level 60 char syncs down to 44
- level 36 fate, level 60 char syncs down to 41
+42
View File
@@ -0,0 +1,42 @@
# FFXIV Icon Paths
The path for icons are very funky, depending on the icon number you need to format the path very specifically, here is a function that does it in PHP which should be easy to translate into other languages:
```php
// Is the number 6 or more characters?
$extended = (strlen($number) >= 6);
// get icon based on number size, prepend 0 if its below 6 characters.
$icon = $extended
? str_pad($number, 5, "0", STR_PAD_LEFT)
: '0' . str_pad($number, 5, "0", STR_PAD_LEFT);
// build path, take values [0, 1, 2] from the number, add 000 for extended
// for non extended start with 0 and add [1,2] + 000 for path.
$path = $extended
? $icon[0] . $icon[1] . $icon[2] .'000'
: '0'. $icon[1] . $icon[2] .'000';
```
The result would be:
- `26039`
- Is less than 5? **yes**
- Folder: `0 26 000` (the 0 we prepended)
- Filename: `026039`
- Result: /026000/026039.png ![Icon!](https://secure.xivdb.com/img/game/026000/026039.png)
XIVDB Icons can be obtained via: `https://secure.xivdb.com/img/game/{id path}`
# XIVDB Icons
> Items only.
It's possible to obtain icons based on their in-game ID, for example:
- Word of the Magnate Replica = `16923`
- Take first number for folder: `1`
- Path: `/img/game_local/ 1 / 16923 .jpg`
- Result: ![Icon!](https://secure.xivdb.com/img/game_local/1/16923.jpg)
The XIVDB icons are not always available, when a new patch comes out it is difficult to get the latest icons.
+164
View File
@@ -0,0 +1,164 @@
### 1013
Mount Barding (data_0 = BuddyEquip)
### 1053
Company Issue Expectorant, Company Issue Tonic, Over-Aspected Cluster, Phoenix Down, all "revive" of some sort
### 1054
Attribute Reset, no longer used
### 1055
Cordials, eg: Watered Cordial, hi cordial - Restores GP
- `data_0` = max GP to restore
### 1058
Used to upgrade an Animus weapon to a Novus weapon
### 1322
Unlock Mount
- `data_0` = `Mount`
### 1326
Fantasia + Retainer Fantasia, data_1 is a bool for "is for retainer"
### 1767
Wings, eg: Icarus Wing, restores TP
- `data_0` = max TP to restore
### 1769
*not used*
### 2120
Cards and Eggs + Starlite giftbox, nothing in data_0, unsure the meaning
### 2136
Unlocks master recipes, (eg Master Carpenter I),
- `data_0` = `SecretRecipeBook`
### 2483
Chocobo Food. Unsure what data_0 is, it's ids 1,2,3,4,5,6,7
### 2633
Unlocks Ballroom emotes, speed boost for mounts on specific maps as well as hair style..
- `data_1` = `LogMessage`
### 2634
Reagan Pepper, Resets chocobos SP
### 2645
Realm Reborn Red, celebrate!
### 2894
Eternity Ring, teleport to partner
### 3237
XXX for beginners, eg: Armoring for Beginners,
- `data_0` = `ClassJob`
### 3240
Blank Invitation, creates a Ceremony Invitation with your wedding date on it
### 3292
Thavnairian Onion - Chocobo food to level it past level 10+ (so every level past 10)
### 3357
Unlock use of Triple Triad Cards
- `data_0` = `TripleTriadCard`
### 3800
Items that grant MGP
- `data_0` = the amount of MGP to reward
### 4107
eg: Tome of Ichthyological Folklore
- `data_0` = `GatheringSubCategory`
### 4647
Unlocking coffers, Wrapped Presents or using Elemental Ward Potions
### 5136
Celebratory fireworks, eg: Heavenscracker
### 5564
Tales of Adventure, boosts to level 60, unsure how the 11->24 links (not class job), 11 = something Paladin related
### 5845
Unlocks a Orchestrion Roll
- `data_0` = `Orchestrion`
### 816
Company Manuals + Squadron Gear Maintenance Manual, unsure what it links to
### 841
Using potions, eg: Paralyzing Potion. Not sure how data_0 links (also has data_hq_0)
### 842
Debuff removal items, eg: Echo Drops, Antitiode, etc.
- `Data_0-8` = `Status`
### 843
Grants ability to discover concealed nodes, eg: Traders' Favor (Dravanian Forelands), unsure how data_1 links
### 844
Battle Food,
- `data_0` (all 48, maybe an animation)??
- `data_1` = `ItemFood`
- `data_2` = Duration in seconds
### 845
Crafting + Gathering Food
- `data_0` (all 48, maybe an animation)??
- `data_1` = `ItemFood`
- `data_2` = Duration in seconds
### 846
Atrtribute Potions, eg: X-Potion of Dexterity
### 847
Health potions, eg: X-Potion
- `data_0` = %
- `data_1` = max value
### 848
Ether MP potions, eg: X-Ether
- `data_0` = %
- `data_1` = max
### 849
Elixir potions,
- HP
- `data_0` = %
- `data_1` = max value
- MP
- `data_3`
- `data_4`
### 850
Sanction effect
### 851
Gysahl Greens, Summon Chocobo
### 852
Consumable effects such as sparklers, etc
### 853
Obtain a minion
- `data_0` = Minion
### 8667
Sustaining Potion, unsure how data_0 links
### 9343
Fate Action.. Stygian Ash, Removes the shield from Ixion
### 944
Magicked Prism
### 9993
"High-grade Company-issue Tonic...", Revives?
### 9994
Teleport ticket to a grand company
- `data_0` = `GrandCompany`
+101
View File
@@ -0,0 +1,101 @@
# Item Stats
Most of these are kind of obvious, but will add here for safe keeping!
# Attack speed
Equation: `(attack speed / 1000 `
# Auto Attack
Equation: `(damage / 3 * attack speed)`
Remember to use HQ damage when determining Auto-Attack for HQ items.
# DPS
Equation: `(damage / 3)`
# Finding HQ values
HQ values are just the difference, the "additional number", if an item is Accuracy: 32, and HQ Accuracy: 35, the HQ value is listed as 3.
An example can be found at offsets: [34, 50, 75 = value, param, hq](https://github.com/viion/XIV-Datamining/blob/master/offsets/Items/items_csv_xivdb_unverified.txt#L15-L17)
# Max meld values
```csharp
class ItemLevel {
public int GetMaximum(BaseParam baseParam) {
const int Offset = -1;
return baseParam.Key == 0 ? 0 : Convert.ToInt32(this[Offset + baseParam.Key]);
}
}
class BaseParam {
public int GetMaximum(EquipSlotCategory category) {
const int Offset = 2;
return category.Key == 0 ? 0 : Convert.ToInt32(this[Offset + category.Key]);
}
public int GetModifier(int role) {
const int Offset = 24;
const int Maximum = 12;
if (role < 0 || role > Maximum)
return 0;
return Convert.ToInt32(this[Offset + role]);
}
}
class Equipment {
public int GetMaximumParamValue(BaseParam baseParam)
// Base value for the param based on the item's level
var maxBase = ItemLevel.GetMaximum(baseParam);
// Factor, in percent, for the param when applied to the item's equip slot
var slotFactor = baseParam.GetMaximum(EquipSlotCategory);
// Factor, in percent, for the param when used for the item's role
var roleModifier = baseParam.GetModifier(BaseParamModifier);
// Rounding appears to use AwayFromZero. Tested with:
// Velveteen Work Gloves (#3601) for gathering (34.5 -> 35)
// Gryphonskin Ring (#4526) for wind resistance (4.5 -> 5)
// Fingerless Goatskin Gloves of Gathering (#3578) for GP (2.5 -> 3)
return (int)Math.Round(maxBase * slotFactor * roleModifier / 10000.0, MidpointRounding.AwayFromZero);
}
}
```
# Overall Item Level
This one is really for gearset applications.
Item level is `*2` when involving two handed weapons, this is to compensate for off hand. You can test this by equipping only a Sword in game, then equip your Shield of the same item level and record the two values. Then equip a two handed weapon, your character will be the same overal item level as a Sword+Shield.
# Category to Slot ID
```
// slot id => cat id
[
// main hand
738 => [
1,2,3,4,5,6,7,8,9,10,84,87,88,89,
12,14,16,18,20,22,24,26,28,30,32,
],
// off hand
739 => [
13,15,17,10,21,23,25,27,29,31,11
],
// head, body, hands, waist, legs, feet, ears, neck, wrists, ring
740 => [34],
741 => [35],
742 => [37],
743 => [39],
744 => [36],
745 => [38],
746 => [41],
747 => [40],
748 => [42],
748 => [43],
];
```
+15
View File
@@ -0,0 +1,15 @@
# Leves to NPC
Leves are linked to an NPC via the levels_csv, this is also the same for the starting position of the Leve.
## Example:
- Leve ID: `906` (Necrologos: His Treasure Forhelen (L))
- NPC ID: `1011208` (Eloin)
npc: `1011208` in levels = id: `5581915`
Then find 5581915 in leves CSV.
- Start npc: Offset `8`
- Start Position: Offset `9`
+105
View File
@@ -0,0 +1,105 @@
# Conversions to 2D Map
### Version of an in-game coordinate to a 2D map coordinate
**By: Clorifex, slightly modified by: Vekien - In C#**
https://github.com/xivapi/xivapi-mappy/blob/master/Mappy/Helpers/MapHelper.cs
```csharp
public static double ConvertCoordinatesIntoMapPosition(double scale, double offset, double val)
{
val = Math.Round(val, 3);
val = (val + offset) * scale;
return ((41.0 / scale) * ((val + 1024.0) / 2048.0)) + 1;
}
```
### Conversion of a Level coordinate into a 2D map coordinate:
**By: Clorifex - In C#**
```csharp
private double ToMapCoordinate(double val) {
var c = Map.SizeFactor / 100.0;
val *= c;
return ((41.0 / c) * ((val + 1024.0) / 2048.0)) + 1;
}
```
### Conversion of a FishingSpot coordinate into a 2D map coordinate:
**By: Clorifex - In C#**
```csharp
private double ToMapCoordinate(double val) {
var c = TerritoryType.Map.SizeFactor / 100.0;
return (41.0 / c) * ((val) / 2048.0) + 1;
}
```
### Conversion of MapPosition to Pixels
**By: Vekien - In C#**
https://github.com/xivapi/xivapi-mappy/blob/master/Mappy/Helpers/MapHelper.cs
```csharp
public static int ConvertMapPositionToPixels(double value, double scale)
{
return Convert.ToInt32((value - 1) * 50 * scale);
}
```
----
To translate a `levels.exd` position to an in-game X/Y:
```php
//
// This takes the X/Y, scale and tilescale from levels and converts it to
// and in-game X/Y position, I have not found tile scale in the files
// and it's never not been 50 (50px per tile), its not 100% accurate,
// tilescale is actually something like 49.951219... (2048/41)
//
public function mapsTranslateXYZToGame($x, $y, $scale, $tilescale = 50)
{
if ($x == 0 || $y == 0 || $scale == 0) {
return false;
}
$map = 2048 / ($scale / 100);
$tilecount = $map / $tilescale;
$x = ceil(round(($x / $tilescale) + ($tilecount / 2),1));
$y = ceil(round(($y / $tilescale) + ($tilecount / 2),1));
return [
'x' => $x,
'y' => $y,
];
}
```
And finally, to translate an in game X/Y to a `levels.exd` position which can then be converted to a 2D or Long/Lat:
```php
//
// Translates a X/Y from in-game to a levels output (which can then be translate dto lat/long
// this is quite accurate, not found it to be off at all. Requires map scale
//
public function mapsTranslateGameToXYZ($x, $y, $scale)
{
$scale = $scale / 100;
$x = ($x*50)-25-(1024 / $scale);
$y = ($y*50)-25-(1024 / $scale);
return [
'x' => $x,
'y' => $y,
];
}
```
+10
View File
@@ -0,0 +1,10 @@
# Maps and various scales
> Do not remember what purpose this had...
ID: 31 = 100 = Lower La Noscea = 41.9
ID: 52 = 200 = New Gridania = 21.4
ID: 58 = 300 = The Tam-Tara Deepcroft = 14.6
ID: 359 = 400 = The Navel = 8.6
ID: 733 = 800 = Mizzenmast Inn = 6.0
ID: 2200 = 95 = Coerthas Western Highlands = 44
+63
View File
@@ -0,0 +1,63 @@
# Quest Exp Reward
Quest experience points are generated using a small equation.
Why this is done I am not sure as the values always seem static.
### Formula updated: 28th May, 2018
**CORE FORMULA**
> `EXP = Quest.ExpFactor * ParamGrow.QuestExpModifier * (45 + (5 * Quest.ClassJobLevel_0)) / 100`
All formula's use this as their starting point and ADD onto it.
**QUEST LEVEL 50**
> `EXP = CORE + ((400 * (Quest.ExpFactor / 100)) + ((Quest.ClassJobLevel_0-52) * (400 * (Quest.ExpFactor/100))))`
**QUEST LEVEL 51**
> `EXP = CORE + ((800 * (Quest.ExpFactor / 100)) + ((Quest.ClassJobLevel_0-52) * (800 * (Quest.ExpFactor/100))))`
**QUEST LEVEL 52-59**
> `EXP = CORE + ((2000 * (Quest.ExpFactor / 100)) + ((Quest.ClassJobLevel_0-52) * (2000 * (Quest.ExpFactor/100))))`
**QUEST LEVEL 60-69**
> `EXP =CORE + ((37125 * (Quest.ExpFactor / 100)) + ((Quest.ClassJobLevel_0-60) * (3375 * (Quest.ExpFactor/100))))`
-----
#### Formula in PHP:
This is example code, it requires getting the `quest` and `paramGrow` prior
```php
$quest = $service->get("Quest_<id>");
$paramGrow = $service->get("xiv_ParamGrow_{$quest->ClassJobLevel_0}");
// CORE = Quest.ExpFactor * ParamGrow.QuestExpModifier * (45 + (5 * Quest.ClassJobLevel_0)) / 100
$EXP = $quest->ExpFactor * $paramGrow->QuestExpModifier * (45 + (5 * $quest->ClassJobLevel_0)) / 100;
// CORE + ((400 * (Quest.ExpFactor / 100)) + ((Quest.ClassJobLevel_0-52) * (400 * (Quest.ExpFactor/100))))
if (in_array($quest->ClassJobLevel_0, [50])) {
$EXP = $EXP + ((400 * ($quest->ExpFactor / 100)) + (($quest->ClassJobLevel_0 - 50) * (400 * ($quest->ExpFactor / 100))));
}
// CORE + ((800 * (Quest.ExpFactor / 100)) + ((Quest.ClassJobLevel_0-52) * (800 * (Quest.ExpFactor/100))))
else if (in_array($quest->ClassJobLevel_0, [51])) {
$EXP = $EXP + ((800 * ($quest->ExpFactor / 100)) + (($quest->ClassJobLevel_0 - 50) * (400 * ($quest->ExpFactor / 100))));
}
// CORE + ((2000 * (Quest.ExpFactor / 100)) + ((Quest.ClassJobLevel_0-52) * (2000 * (Quest.ExpFactor/100))))
else if (in_array($quest->ClassJobLevel_0, [52,53,54,55,56,57,58,59])) {
$EXP = $EXP + ((2000 * ($quest->ExpFactor / 100)) + (($quest->ClassJobLevel_0 - 52) * (2000 * ($quest->ExpFactor / 100))));
}
// CORE + ((37125 * (Quest.ExpFactor / 100)) + ((Quest.ClassJobLevel_0-60) * (3375 * (Quest.ExpFactor/100))))
else if (in_array($quest->ClassJobLevel_0, [60,61,62,63,64,65,66,67,68,69])) {
$EXP = $EXP + ((37125 * ($quest->ExpFactor / 100)) + (($quest->ClassJobLevel_0 - 60) * (3375 * ($quest->ExpFactor / 100))));
}
// Set
$quest->ExperiencePoints = $EXP;
```
+66
View File
@@ -0,0 +1,66 @@
# FFXIV Data-Mining
- Research
- [Achievements Requirement Types](docs/AchievementsRequirementTypes.md)
- [Collectable Reward Info](docs/CollectibleRewardInfo.md)
- [CompanionApp API](docs/CompanionAppApi.md)
- [Emote Log Messages](docs/EmoteLogMessage.md)
- [FATE Experience Points](docs/FateExpPoints.md)
- [FATE Levels](docs/FateLevels.md)
- [Icon Paths](docs/IconPaths.md)
- [Item Actions](docs/ItemActions.md)
- [Item Stats](docs/ItemStats.md)
- [LeveToNpc](docs/LeveToNpc.md)
- [MapCoordinates](docs/MapCoordinates.md)
- [MapScales](docs/MapScales.md)
- [QuestExpReward](docs/QuestExpReward.md)
- [UIColor](docs/UIColor.md)
- [Weather](docs/Weather.md)
- Tools of the trade
- [SaintCoinach](https://github.com/ufx/SaintCoinach) - The #1 datamining tool.
- [TextTools2](https://github.com/liinko/FFXIV_TexTools2) - Extract data, import game mods.
- [Sapphire Server Emulation](https://github.com/SapphireMordred/Sapphire) - FFXIV Server Emulation
- [Machina](https://github.com/ravahn/machina) - Network Packet Reader
- [Sharlayan](https://github.com/FFXIVAPP/sharlayan) - In-Game memory reader.
- [FFXIV MemoryReader](https://github.com/TamanegiMage/FFXIV_MemoryReader) - In-Game memory reader.
- [XIVAPI.com Source Code](https://github.com/xivapi/xivapi.com)
- [LibPomPom](https://github.com/Minoost/libpompom-sharp) - C# code that authenticates with the FFXIV Companion App.
- [3D Web Model Viewer](https://github.com/viion/ffxiv-3d)
- [Sapphire Ubuntu Setup Script](https://github.com/viion-misc/ffxiv-sapphire-setup) - a script to install Sapphire on ubuntu.
- [Custom FFXIV Launcher](https://github.com/xivapi/ffxiv-launcher)
- [FFXIV Mapping Tool](https://github.com/xivapi/xivapi-mappy) - Map Eorzea by grabbing position data of NPCs, Enemies and EObjs.
- [Lodestone Parser](https://github.com/xivapi/lodestone-parser) - PHP library that parses lodestone.
- [ClassJob Icons](https://github.com/xivapi/xivapi-classjobs) - A collection of HQ job class icons.
- [FFXIV Icons](http://xivapi.com/docs/Icons) - Download all sorts of FFXIV related icons.
- [XIVAPI Quick Search](https://chrome.google.com/webstore/detail/xivapi-quick-search/lgefpgdbbmcahllbifniibndmoignmfg) - Search XIVAPI via a chrome extension!
- [REST API](http://xivapi.com/) - A little REST API that has a few bits and bobs.
- Archives
- [Extract of every FFXIV Patch since 2.0](https://github.com/viion/ffxiv-datamining-patches)
- Dead
- [Data Tool](https://github.com/viion-misc/xivdb-data-tool) - A python library that extracts game data.
- [Libra PHP](https://github.com/viion-misc/libra-php) - PHP library that extracts data from the FFXIV Libra app.
- Discords:
- [XIVAPI](https://discord.gg/MFFVHWC)
- [GarlandTools](https://discord.gg/)
- [TeamCraft](https://discord.gg/493Z6pQ)
- [Sapphire](https://discord.gg/xxcdCER)
- [Apkallu Falls](https://discord.gg/RNcV4eY)
Do you have a discord for your FFXIV project? Provide a PR with your invite link added!
---
# Community Members
Notable members that have contributed information in some way, If you're part of the datamining and FFXIV discovery community be sure to add your credit!
- Vekien (xivapi)
- Clorifex (GarlandTools)
- Hezkezl (GamerEscape)
- Icarus Twine (GamerEscape & Blue Mage overlord)
- Miu (TeamCraft)
- Ioncannon (FFXIV Explorer)
+124
View File
@@ -0,0 +1,124 @@
# UI Color
### Update 3rd October, 2018
It is believed that:
- 72 = Foreground
- 73 = Glow (not background)
In Patch 4.4, SE changed how colours work in tooltips in preperation of new UI "Skins". The logic for displaying colours changed quite drastically but was figured out quickly, here is the process and how it works now:
## (ID: 174) Bane tooltip
![](https://cdn.discordapp.com/attachments/474519195963490305/491551208989917187/unknown.png)
**Old tooltip logic**
```
Spread a target's <Color(-154)>Bio</Color> and <Color(-154)>Miasma</Color>
effects (except <Color(-154)>Miasma II</Color>) to nearby enemies.
```
This is now:
```
Spread a target's <72>F201FA</72><73>F201FB</73>Bio<73>01</73><72>01</72>
and <72>F201FA</72><73>F201FB</73>Miasma<73>01</73><72>01</72> effects
(except <72>F201FA</72><73>F201FB</73>Miasma II<73>01</73><72>01</72>) to nearby enemies.
```
And this is how it should be when converted:
```
Spread a target's <span style="color:#ffff66;">Bio</span> and
<span style="color:#ffff66;">Miasma</span> effects (except
<span style="color:#ffff66;">Miasma II</span>) to nearby enemies.
```
Taking a small snippet:
- Before: `<span style="color:#ffff66;">Bio</span>`
- After: `<72>F201FA</72><73>F201FB</73>Bio<73>01</73><72>01</72>`
> At this time we're not sure what 73 is for.
Focusing on these values, **we need to remove the first byte: F2**
- `<72>01FA</72><73>01FB</73>Bio<73>01</73><72>01</72>`
**After that, convert the HEX values to DEC:**
- `<72>506</72><73>507</73>Bio<73>01</73><72>01</72>`
In Patch 4.4 a new file was added: [UIColor.csv](https://github.com/viion/ffxiv-datamining/blob/master/csv/UIColor.csv)
If our guess of Foreground/Background is correct, then in `UIColor`: Col 1 = Foreground and Col 2 = Background, grabbing the rows related to our decimal values and the correct column we get:
- `<72>4294928127</72><73>4294174719</73>Bio<73>01</73><72>01</72>`
**And we can convert these values from DEC to HEX:**
- `<72>FFFF66FF</72><73>FFF3E7FF</73>Bio<73>01</73><72>01</72>`
The hex can contain the alpha transparency, so if the value is a 8 character hex, **we only care about the first 6.**
- `<72>FFFF66</72><73>FFF3E7</73>Bio<73>01</73><72>01</72>`
The 01 are always closing colours (resetting back to default text colors) so we can just replace those with generic closing tags and replace the opening tags to be HTML-like:
- `<72 = FFFF66><73 = FFF3E7>Bio</73></72>`
And now it's an easy convert to html:
- `<span style="color:#FFFF66;"><span style="color:#FFF3E7;">Bio</span></span>`
Remove 73 as it doesn't seem to do much right now:
- Before: `<span style="color:#ffff66;">Bio</span>`
- After: `<span style="color:#FFFF66;">Bio</span>`
*And there we have it!*
---
Some testing html:
```
<main>
<div>
default <span style="color:#FFFF66;"><span style="color:#FFF3E7;">Bio</span></span>
</div>
<div>
No 73 (correct look) <span style="color:#FFFF66;">Bio</span>
</div>
<div>
No 72 <span style="color:#FFF3E7;">Bio</span>
</div>
<div>
72 = fore, 73 = back <span style="color:#FFFF66;"><span style="background-color:#FFF3E7;">Bio</span></span>
</div>
<div>
72 = fore, 73 = glow <span style="color:#FFFF66;"><span style="text-shadow: 0 0 3px #FFF3E7;">Bio</span></span>
</div>
</main>
```
---
Test Data
```
Spreads a target's <72>F201FA</72>Bio<72>01</72> and <72>F201FA</72>Miasma<72>01</72> effects to nearby enemies.</If><Else/>Spreads a target's <72>F201FA</72>Bio<72>01</72> and <72>F201FA</72>Miasma<72>01</72> effects to nearby enemies.</If> Potency is reduced by 20% for the second enemy, 40% for the third, 60% for the fourth, and 80% for all remaining enemies. <72>F201F8</72>Duration:<72>01</72> Time remaining on original effect <72>F201F8</72>Additional Effect:<72>01</72> 15% chance that <72>F201FA</72>Bio<72>01</72> and <72>F201FA</72>Miasma<72>01</72> duration resets if shorter than original effect duration <If(GreaterThanOrEqualTo(PlayerParameter(72),58))><If(Equal(PlayerParameter(68),27))><72>F201F8</72>Additional Effect:<72>01</72> <72>F201FA</72>Aethertrail Attunement<72>01</72> <Else/></If><Else/></If><If(GreaterThanOrEqualTo(PlayerParameter(72),70))><If(Equal(PlayerParameter(68),28))><72>F201F8</72>Additional Effect:<72>01</72> Increases <72>F201F4</72>Faerie Gauge<72>01</72> by 10 <Else/></If><Else/></If><72>F201F8</72>Aetherflow Gauge Cost:<72>01</72> 1
Spread a target's <Color(-154)>Bio</Color> and <Color(-154)>Miasma</Color> effects (except <Color(-154)>Miasma II</Color>) to nearby enemies.<Else/>Spreads a target's <Color(-154)>Bio</Color> and <Color(-154)>Miasma</Color> effects to nearby enemies.</If><Else/>Spreads a target's <Color(-154)>Bio</Color> and <Color(-154)>Miasma</Color> effects to nearby enemies.</If>
Potency is reduced by 20% for the second enemy, 40% for the third, 60% for the fourth, and 80% for all remaining enemies.
<Color(52258)>Duration:</Color> Time remaining on original effect
<Color(52258)>Additional Effect:</Color> 15% chance that <Color(-154)>Bio</Color> and <Color(-154)>Miasma</Color> duration resets if shorter than original effect duration
<If(GreaterThanOrEqualTo(PlayerParameter(72),58))><If(Equal(PlayerParameter(68),27))><Color(52258)>Additional Effect:</Color> <Color(-154)>Aethertrail Attunement</Color>
<Else/></If><Else/></If><If(GreaterThanOrEqualTo(PlayerParameter(72),70))><If(Equal(PlayerParameter(68),28))><Color(52258)>Additional Effect:</Color> Increases <Color(-34022)>Faerie Gauge</Color> by 10
<Else/></If><Else/></If><Color(52258)>Aetherflow Gauge Cost:</Color> 1
Spread a target's <span style="color:#ffff66;">Bio</span> and <span style="color:#ffff66;">Miasma</span> effects (except <span style="color:#ffff66;">Miasma II</span>) to nearby enemies. Potency is reduced by 20% for the second enemy, 40% for the third, 60% for the fourth, and 80% for all remaining enemies.n<span style="color:#00cc22;">Duration:</span> Time remaining on original effectn<span style="color:#00cc22;">Additional Effect:</span> 15% chance that <span style="color:#ffff66;">Bio</span> and <span style="color:#ffff66;">Miasma</span> duration resets if shorter than original effect duration <span style="color:#00cc22;">Additional Effect:</span> <span style="color:#ffff66;">Aethertrail Attunement</span> <span style="color:#00cc22;">Additional Effect:</span> Increases <span style="color:#ff7b1a;">Faerie Gauge</span> by 10 <span style="color:#00cc22;">Aetherflow Gauge Cost:</span> 1
```
+37
View File
@@ -0,0 +1,37 @@
# Weather
The current weather of a zone is based on the current unix epoch, and a zone weather table (WeatherRate).
A forecast target integer is first calculated. This value to the frequency of the zone's weather.
When the number is less than the cumulative rate at which a weather pattern occurs, that weather will appear in the zone.
For example, in Gridania it's raining 20% of the time, and foggy 10%. It rains when the number is < 20, and foggy when it's >= 20 and < 30, and so on.
````js
calculateForecastTarget: function(lDate) {
// Thanks to Rogueadyn's SaintCoinach library for this calculation.
// lDate is the current local time.
var unixSeconds = parseInt(lDate.getTime() / 1000);
// Get Eorzea hour for weather start
var bell = unixSeconds / 175;
// Do the magic 'cause for calculations 16:00 is 0, 00:00 is 8 and 08:00 is 16
var increment = (bell + 8 - (bell % 8)) % 24;
// Take Eorzea days since unix epoch
var totalDays = unixSeconds / 4200;
totalDays = (totalDays << 32) >>> 0; // Convert to uint
// 0x64 = 100
var calcBase = totalDays * 100 + increment;
// 0xB = 11
var step1 = (calcBase << 11) ^ calcBase;
var step2 = (step1 >>> 8) ^ step1;
// 0x64 = 100
return step2 % 100;
}
````