├── .classpath
├── .gitignore
├── .project
├── LICENSE
├── README.md
├── example.png
└── src
├── Main.java
├── boon
├── AbstractBoon.java
├── BoonFactory.java
├── Duration.java
└── Intensity.java
├── data
├── AgentData.java
├── AgentItem.java
├── BossData.java
├── CombatData.java
├── CombatItem.java
├── LogData.java
├── SkillData.java
└── SkillItem.java
├── enums
├── Activation.java
├── Agent.java
├── Boon.java
├── BuffRemove.java
├── CustomSkill.java
├── IFF.java
├── MenuChoice.java
├── Result.java
└── StateChange.java
├── player
├── BoonLog.java
├── DamageLog.java
└── Player.java
├── statistics
├── Parse.java
└── Statistics.java
└── utility
├── TableBuilder.java
└── Utility.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | org.eclipse.jdt.core.javabuilder
6 |
7 |
8 |
9 |
10 |
11 | org.eclipse.jdt.core.javanature
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 phoenix-oosd
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EVTC Log Parser #
2 |
3 | ## About ##
4 |
5 | The project was started to help re-live boss encounters and identify areas for improvement in my own organised group raids. The resultant program is a parser for ` .evtc ` event chain logs created by [arcdps](https://www.deltaconnected.com/arcdps/).
6 | It is written in Java 8 and requires an installation of [JRE 1.8](https://www.java.com/en/download/), but in most cases you probably already have it installed.
7 |
8 | ## User Manual ##
9 |
10 | ### Setting Up ###
11 |
12 | Because of the way Java works in tandem with Windows, you don't want to run the program by just double-clicking ` evtc_log_parser.jar `. Instead you will have to run using the supplied `run .bat`, but more on this later. First you want to create a folder to contain the two files.
13 |
14 | On the very first run, the following folders are created in the launch directory: `/logs/`, `/graphs/`, and `/tables/`. You will want to re-run the program after it detects `/logs/` is empty. Copy .evtc file(s) for parsing into /logs/. The program will recursively search `/logs/` and its sub-directories for `.evtc files`. Both `/graphs/` and `/tables/` are output folders for the related options.
15 |
16 | ### Basic Use ###
17 |
18 | For basic use you can double click `run .bat` which will open up a console with a menu. Enter the option you want by number (e.g. 1 for Final DPS) and press Enter to confirm. Each ` .evtc ` file in `/logs/` will be processed. The results will be displayed directly into the console, or be directed to files in the sub-directories where appropriate.
19 |
20 | ### Locating the Logs ###
21 |
22 | The ` .evtc ` files are automatically created by ` arcdps ` at the end of encounters.
23 | Your files can be found at ` Documents\arcdps.cbtlogs `, each sub-directory corresponds to a different encounter.
24 | Consult the table below to find the logs you need want to analyse.
25 |
26 |
27 | | Folder | Boss |
28 | | ------------- |---------------------------|
29 | | 15438 | Vale Guardian |
30 | | 15429 | Gorseval the Multifarious |
31 | | 15375 | Sabetha the Saboteur |
32 | | 16123 | Slothasor |
33 | | 16088 | Berg |
34 | | 16137 | Zane |
35 | | 16125 | Narella |
36 | | 16115 | Matthias Gabrel |
37 | | 16235 | Keep Construct |
38 | | 16246 | Xera |
39 | | 17194 | Cairn |
40 | | 17172 | Mursaat Overseer |
41 | | 17188 | Samarog |
42 | | 17154 | Deimos |
43 |
44 | ### File Association ###
45 |
46 | Double clicking any ` .evtc ` file will display an ` Open with... ` dialogue. Tick ` Always use this app to open .evtc files` and choose ` run.bat`. Now, whenever you double-click an ` .evtc ` file, the file will be parsed on the spot based on the `options` command argument. The default argument is ` options=516 ` which displays the ` Miscellaneous Combat Statistics`, ` Final DPS `, and ` Final Boons ` in that order. Option 4 and 8 do not work with file association.
47 |
48 | ### Output Customisation ###
49 |
50 | You can customise the parsing to your liking by opening `run .bat` in a text editor such as `Notepad`.
51 |
52 | You will notice everything is on a single line prefixed by ` start "EVTC Log Parser" /MAX `. If you don't want to maximise the console you can delete this, but it is highly recommended to maximise the console so text does not wrap around.
53 |
54 | The `java -jar "path"` section is required and you *MUST* make sure you have an absolute path to ` evtc_log_parser.jar` (e.g. `C:\Users\JohnDoe\Desktop\EVTC Log Parser\evtc_log_parser.jar`). On Windows, the default path will only work if you create a desktop folder named "EVTC Log Parser" on your desktop, and move the attached files from the latest release into it.
55 |
56 | After the path is specified you can add arguments in any order by space separating `arg_name=value`. For convenience all the arguments have already been written for you.
57 |
58 | The program has the following general arguments that apply to both basic running and file association:
59 |
60 | 1. is_anon
61 | * The default value is `0`
62 | * You can edit this string to `1` to hide all account/player names
63 |
64 | The program has the following arguments specific to file association:
65 |
66 | 1. file_path
67 | * The default value is `%1`
68 | * *NEVER* change or remove this argument as it is required for file association.
69 | 2. options
70 | * The default value is `5126`
71 | * *NEVER* remove this argument as it is required for file association
72 | * You can edit this string to match any of the available options below to display tables in a certain order
73 |
74 |
75 | ### Options ###
76 |
77 | All DPS numbers are derived from the players (and pets) to *ONLY* the boss. Phases are sections of the fight when the boss is vulnerable to damage. This only applies for encounters which consist of predictable phases, so not Keep Construct.
78 |
79 | The program has the following options:
80 |
81 | 0. Text Dump
82 | * For debugging, or if you want to see a human readable version of the log
83 | 1. Final DPS
84 | * DPS by player and group
85 | * Damage dealt by each player and group
86 | 2. Phase DPS
87 | * DPS for each phase where applicable
88 | * Phase duration
89 | 3. Damage Distribution - ranks damage output by skill by each player
90 | * Damage breakdown of each player by skill
91 | * Ranks skills in order of contribution
92 | 4. Total Damage Graph
93 | * Graphs the damage
94 | * Can be used to identify mechanical portions of the fight (e.g. flat lines for Matthias sacrifices)
95 | 5. Miscellaneous Combat Statistics
96 | * Healing, Toughness and Condition damage of each player on a scale of 1-10
97 | * Fight rates such as Scholar up-time, seaweed salad movement, and critical rates
98 | 6. Final Boons
99 | * Show relevant boon up-time
100 | * Show relevant class buff up-time
101 | 7. Phase Boons
102 | * Boons for each phase where applicable
103 | 8. Text Dump Tables
104 | * Saves options 1, 2, 3, 5, 6, and 7 tables into ` /tables/ `
105 |
106 | ### Interpreting Output ###
107 |
108 | 
109 |
110 | ## Future ##
111 |
112 | ### Known Problems ###
113 |
114 | -Phase related statistics if you don't reach the final phase of the boss
115 |
116 | -This program has only been tested on Windows 10
117 |
118 | -If there are any problems not listed here, contact me via PM on [reddit](https://www.reddit.com/user/ghandi-gandhi) or GW2 (Phoenix.5719)
119 |
120 | ### Working on... ###
121 |
122 | -Dynamic phase detection for non-linear fights like Keep Construct
123 |
124 | -Damage taken statistics
125 |
126 |
--------------------------------------------------------------------------------
/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-oosd/EVTC-Log-Parser/dbd36710802f77350ade637d40c82c0a68e34a72/example.png
--------------------------------------------------------------------------------
/src/Main.java:
--------------------------------------------------------------------------------
1 | import java.io.File;
2 | import java.io.IOException;
3 | import java.nio.file.Path;
4 | import java.nio.file.Paths;
5 | import java.util.ArrayList;
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Scanner;
10 |
11 | import enums.MenuChoice;
12 | import statistics.Parse;
13 | import statistics.Statistics;
14 | import utility.TableBuilder;
15 | import utility.Utility;
16 |
17 | public class Main
18 | {
19 |
20 | // Fields
21 | private static boolean will_quit = false;
22 | private static boolean displaying = true;
23 | private static Map argument_map = new HashMap<>();
24 | private static String old_version = Utility.boxText("ERROR : This only supports versions 20170218 and onwards");
25 | private static String current_file;
26 | private static Parse parsed_file;
27 | private static Statistics statistics;
28 |
29 | // Main
30 | public static void main(String[] args)
31 | {
32 | // Scanner
33 | Scanner scan = null;
34 | try
35 | {
36 | scan = new Scanner(System.in);
37 |
38 | // Read arguments
39 | for (String arg : args)
40 | {
41 | if (arg.contains("="))
42 | {
43 | argument_map.put(arg.substring(0, arg.indexOf('=')), arg.substring(arg.indexOf('=') + 1));
44 | }
45 | }
46 | String is_anon = argument_map.get("is_anon");
47 | String is_displaying = argument_map.get("is_displaying");
48 | String file_path = argument_map.get("file_path");
49 | String options = argument_map.get("options");
50 |
51 | // Handle arguments
52 | if (is_anon != null)
53 | {
54 | Statistics.hiding_players = Utility.toBool(Integer.valueOf(is_anon));
55 | }
56 | if (is_displaying != null)
57 | {
58 | displaying = Utility.toBool(Integer.valueOf(is_displaying));
59 | }
60 |
61 | // File Association
62 | if (file_path != null && !file_path.isEmpty() && options != null)
63 | {
64 | int[] choices = options.chars().map(x -> x - '0').toArray();
65 |
66 | StringBuilder output = new StringBuilder();
67 | if (displaying)
68 | {
69 | System.out.println((Utility.boxText("Log Data")));
70 | }
71 | for (int i : choices)
72 | {
73 | MenuChoice c = MenuChoice.getEnum(i);
74 | if (c != null && c.canBeAssociated())
75 | {
76 | String result = parseFileByChoice(c, Paths.get(file_path));
77 | if (result.contains("ERROR"))
78 | {
79 | System.out.println(old_version);
80 | scan.nextLine();
81 | return;
82 | }
83 | else
84 | {
85 | output.append(result + System.lineSeparator());
86 | }
87 | }
88 | }
89 | System.out.println(output.toString());
90 | scan.nextLine();
91 | return;
92 | }
93 |
94 | // Menu
95 | else
96 | {
97 | // Create required directories
98 | new File("./logs").mkdir();
99 | new File("./graphs").mkdirs();
100 | new File("./tables").mkdirs();
101 |
102 | // Obtain list of .evtc files in /logs/
103 | List log_files = new ArrayList();
104 | File log_folder = new File("./logs");
105 | Utility.recursiveFileSearch(log_folder, log_files);
106 |
107 | // No logs to process
108 | if (log_files.isEmpty())
109 | {
110 | try
111 | {
112 | System.out.println(Utility.boxText("ERROR : No log files found at \""
113 | + log_folder.getCanonicalPath().toString() + "\" ... press Enter to exit"));
114 | } catch (IOException e)
115 | {
116 | e.printStackTrace();
117 | }
118 | scan.nextLine();
119 | return;
120 | }
121 | // There are logs to process
122 | else
123 | {
124 | while (!will_quit)
125 | {
126 | // Display menu
127 | System.out.println("\u250C" + Utility.fillWithChar(24, '\u2500') + "\u2510");
128 | System.out.println("\u2502 EVTC Log Parser \u2502");
129 | System.out.println("\u251C" + Utility.fillWithChar(24, '\u2500') + "\u2524");
130 | System.out.println("\u2502 0. Dump EVTC \u2502");
131 | System.out.println("\u2502 1. Final DPS \u2502");
132 | System.out.println("\u2502 2. Phase DPS \u2502");
133 | System.out.println("\u2502 3. Damage Distribution \u2502");
134 | System.out.println("\u2502 4. Graph Total Damage \u2502");
135 | System.out.println("\u2502 5. Misc. Combat Stats \u2502");
136 | System.out.println("\u2502 6. Final Boon Rates \u2502");
137 | System.out.println("\u2502 7. Phase Boon Rates \u2502");
138 | System.out.println("\u2502 8. Dump All Tables \u2502");
139 | System.out.println("\u2502 9. Quit \u2502");
140 | System.out.println("\u2514" + Utility.fillWithChar(24, '\u2500') + "\u2518");
141 |
142 | // Read user input
143 | MenuChoice choice = null;
144 | System.out.println(Utility.boxText("Enter an option by number"));
145 | System.out.print(" >> ");
146 | if (scan.hasNextInt())
147 | {
148 | System.out.println(System.lineSeparator() + Utility.fillWithChar(50, '\u2500')
149 | + System.lineSeparator());
150 | choice = MenuChoice.getEnum(scan.nextInt());
151 | }
152 | scan.nextLine();
153 |
154 | // Invalid option
155 | if (choice == null)
156 | {
157 | System.out.println(Utility.boxText("WARNING : Invalid option"));
158 | }
159 | // Quit
160 | else if (choice.equals(MenuChoice.QUIT))
161 | {
162 | will_quit = true;
163 | }
164 | // Valid option
165 | else
166 | {
167 | // Apply option to all logs
168 | for (Path log : log_files)
169 | {
170 | System.out.println(Utility.boxText("INPUT : " + log.getFileName().toString()));
171 | String output = parseFileByChoice(choice, log);
172 | System.out.println(output + System.lineSeparator() + System.lineSeparator()
173 | + Utility.fillWithChar(50, '\u2500') + System.lineSeparator());
174 | }
175 | }
176 | }
177 | }
178 | }
179 | }
180 |
181 | // Close scanner
182 | finally
183 | {
184 | if (scan != null)
185 | {
186 | scan.close();
187 | }
188 | }
189 | return;
190 | }
191 |
192 | private static String parseFileByChoice(MenuChoice choice, Path path)
193 | {
194 | // Parse a new file
195 | if (current_file == null || !current_file.equals(path.getFileName().toString().split("\\.")[0]))
196 | {
197 | try
198 | {
199 | parsed_file = new Parse(path.toString());
200 | statistics = new Statistics(parsed_file);
201 | current_file = path.getFileName().toString().split("\\.")[0];
202 | if (displaying)
203 | {
204 | TableBuilder table = new TableBuilder();
205 | table.addRow("Build Version", "Point of View", "Start Date", "End Date");
206 | table.addRow(parsed_file.getLogData().toStringArray());
207 | System.out.println(table.toString());
208 | }
209 | if (Integer.valueOf(parsed_file.getLogData().getBuildVersion().replaceAll("EVTC", "")) < 20170218)
210 | {
211 | return old_version;
212 | }
213 | } catch (IOException e)
214 | {
215 | e.printStackTrace();
216 | }
217 | }
218 |
219 | // Apply option
220 | if (choice.equals(MenuChoice.FINAL_DPS))
221 | {
222 | return statistics.getFinalDPS();
223 | }
224 | else if (choice.equals(MenuChoice.PHASE_DPS))
225 | {
226 | return statistics.getPhaseDPS();
227 | }
228 | else if (choice.equals(MenuChoice.DMG_DIST))
229 | {
230 | return statistics.getDamageDistribution();
231 | }
232 | else if (choice.equals(MenuChoice.G_TOTAL_DMG))
233 | {
234 | return Utility.boxText("OUTPUT : " + statistics.getTotalDamageGraph(current_file));
235 | }
236 | else if (choice.equals(MenuChoice.MISC_STATS))
237 | {
238 | return statistics.getCombatStatistics();
239 | }
240 | else if (choice.equals(MenuChoice.FINAL_BOONS))
241 | {
242 | return statistics.getFinalBoons();
243 | }
244 | else if (choice.equals(MenuChoice.PHASE_BOONS))
245 | {
246 | return statistics.getPhaseBoons();
247 | }
248 | else if (choice.equals(MenuChoice.DUMP_EVTC))
249 | {
250 | File evtc_dump = new File(
251 | "./tables/" + current_file + "_" + parsed_file.getBossData().getName() + "_evtc-dump.txt");
252 | try
253 | {
254 | Utility.writeToFile(parsed_file.toString(), evtc_dump);
255 | } catch (IOException e)
256 | {
257 | e.printStackTrace();
258 | }
259 | return Utility.boxText("OUTPUT : " + evtc_dump.getName());
260 | }
261 | else if (choice.equals(MenuChoice.DUMP_TABLES))
262 | {
263 | File text_dump = new File(
264 | "./tables/" + current_file + "_" + parsed_file.getBossData().getName() + "_all-tables.txt");
265 | try
266 | {
267 | Utility.writeToFile(statistics.getFinalDPS() + System.lineSeparator() + statistics.getPhaseDPS()
268 | + System.lineSeparator() + statistics.getCombatStatistics() + System.lineSeparator()
269 | + statistics.getFinalBoons() + System.lineSeparator() + statistics.getPhaseBoons()
270 | + System.lineSeparator() + statistics.getDamageDistribution(), text_dump);
271 | } catch (IOException e)
272 | {
273 | e.printStackTrace();
274 | }
275 | return Utility.boxText("OUTPUT : " + text_dump.getName());
276 | }
277 | return "";
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/src/boon/AbstractBoon.java:
--------------------------------------------------------------------------------
1 | package boon;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | public abstract class AbstractBoon {
8 |
9 | // Fields
10 | protected List boon_stack = new ArrayList();
11 | protected int capacity;
12 |
13 | // Constructor
14 | public AbstractBoon(int capacity) {
15 | this.capacity = capacity;
16 | }
17 |
18 | // Abstract Methods
19 | public abstract int getStackValue();
20 |
21 | public abstract void update(int time_passed);
22 |
23 | public abstract void addStacksBetween(List boon_stacks, int time_between);
24 |
25 | // Public Methods
26 | public void add(int boon_duration) {
27 | // Find empty slot
28 | if (!isFull()) {
29 | boon_stack.add(boon_duration);
30 | sort();
31 | }
32 | // Replace lowest value
33 | else {
34 | int index = boon_stack.size() - 1;
35 | if (boon_stack.get(index) < boon_duration) {
36 | boon_stack.set(index, boon_duration);
37 | sort();
38 | }
39 | }
40 | }
41 |
42 | // Protected Methods
43 | protected boolean isFull() {
44 | return boon_stack.size() >= capacity;
45 | }
46 |
47 | protected void sort() {
48 | Collections.sort(boon_stack, Collections.reverseOrder());
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/boon/BoonFactory.java:
--------------------------------------------------------------------------------
1 | package boon;
2 |
3 | import enums.Boon;
4 |
5 | public class BoonFactory {
6 |
7 | // Factory
8 | public AbstractBoon makeBoon(Boon boon) {
9 | if (boon.getType().equals("intensity")) {
10 | return new Intensity(boon.getCapacity());
11 | } else if (boon.getType().equals("duration")) {
12 | return new Duration(boon.getCapacity());
13 | } else {
14 | return null;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/boon/Duration.java:
--------------------------------------------------------------------------------
1 | package boon;
2 |
3 | import java.util.List;
4 |
5 | public class Duration extends AbstractBoon {
6 |
7 | // Constructor
8 | public Duration(int capacity) {
9 | super(capacity);
10 | }
11 |
12 | // Public Methods
13 | @Override
14 | public int getStackValue() {
15 | return boon_stack.stream().mapToInt(Integer::intValue).sum();
16 | }
17 |
18 | @Override
19 | public void update(int time_passed) {
20 |
21 | if (!boon_stack.isEmpty()) {
22 | // Clear stack
23 | if (time_passed >= getStackValue()) {
24 | boon_stack.clear();
25 | return;
26 | }
27 | // Remove from the longest duration
28 | else {
29 | boon_stack.set(0, boon_stack.get(0) - time_passed);
30 | if (boon_stack.get(0) <= 0) {
31 | // Spend leftover time
32 | time_passed = Math.abs(boon_stack.get(0));
33 | boon_stack.remove(0);
34 | update(time_passed);
35 | }
36 | }
37 | }
38 | }
39 |
40 | @Override
41 | public void addStacksBetween(List boon_stacks, int time_between) {
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/boon/Intensity.java:
--------------------------------------------------------------------------------
1 | package boon;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.Iterator;
6 | import java.util.List;
7 |
8 | public class Intensity extends AbstractBoon {
9 |
10 | // Constructor
11 | public Intensity(int capacity) {
12 | super(capacity);
13 | }
14 |
15 | // Public Methods
16 | @Override
17 | public int getStackValue() {
18 | return boon_stack.size();
19 | }
20 |
21 | @Override
22 | public void update(int time_passed) {
23 |
24 | // Subtract from each
25 | for (int i = 0; i < boon_stack.size(); i++) {
26 | boon_stack.set(i, boon_stack.get(i) - time_passed);
27 | }
28 | // Remove negatives
29 | for (Iterator iter = boon_stack.listIterator(); iter.hasNext();) {
30 | Integer stack = iter.next();
31 | if (stack <= 0) {
32 | iter.remove();
33 | }
34 | }
35 | }
36 |
37 | @Override
38 | public void addStacksBetween(List boon_stacks, int time_between) {
39 |
40 | // Create copy of the boon
41 | Intensity boon_copy = new Intensity(this.capacity);
42 | boon_copy.boon_stack = new ArrayList(this.boon_stack);
43 | List stacks = boon_copy.boon_stack;
44 |
45 | // Simulate the boon stack decreasing
46 | if (!stacks.isEmpty()) {
47 |
48 | int time_passed = 0;
49 | int min_duration = Collections.min(stacks);
50 |
51 | // Remove minimum duration from stack
52 | for (int i = 1; i < time_between; i++) {
53 | if ((i - time_passed) >= min_duration) {
54 | boon_copy.update(i - time_passed);
55 | if (!stacks.isEmpty()) {
56 | min_duration = Collections.min(stacks);
57 | }
58 | time_passed = i;
59 | }
60 | boon_stacks.add(boon_copy.getStackValue());
61 | }
62 | }
63 | // Fill in remaining time with 0 values
64 | else {
65 | for (int i = 1; i < time_between; i++) {
66 | boon_stacks.add(0);
67 | }
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/data/AgentData.java:
--------------------------------------------------------------------------------
1 | package data;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import enums.Agent;
7 |
8 | public class AgentData {
9 |
10 | // Fields
11 | private List player_agent_list = new ArrayList();
12 | private List NPC_agent_list = new ArrayList();
13 | private List gadget_agent_list = new ArrayList();
14 | private List all_agents_list = new ArrayList();
15 |
16 | // Constructors
17 | public AgentData() {
18 | }
19 |
20 | // Public Methods
21 | public void addItem(Agent agent, AgentItem item) {
22 | if (agent.equals(Agent.NPC)) {
23 | NPC_agent_list.add(item);
24 | } else if (agent.equals(Agent.GADGET)) {
25 | gadget_agent_list.add(item);
26 | } else {
27 | player_agent_list.add(item);
28 | }
29 | all_agents_list.add(item);
30 | }
31 |
32 | // Getters
33 | public List getPlayerAgentList() {
34 | return player_agent_list;
35 | }
36 |
37 | public List getNPCAgentList() {
38 | return NPC_agent_list;
39 | }
40 |
41 | public List getGadgetAgentList() {
42 | return gadget_agent_list;
43 | }
44 |
45 | public List getAllAgentsList() {
46 | return all_agents_list;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/data/AgentItem.java:
--------------------------------------------------------------------------------
1 | package data;
2 |
3 | public class AgentItem {
4 |
5 | // Fields
6 | private long agent;
7 | private int instid = 0;
8 | private int first_aware = 0;
9 | private int last_aware = Integer.MAX_VALUE;
10 | private String name;
11 | private String prof;
12 | private int toughness = 0;
13 | private int healing = 0;
14 | private int condition = 0;
15 |
16 | // Constructors
17 | public AgentItem(long agent, String name, String prof) {
18 | this.agent = agent;
19 | this.name = name;
20 | this.prof = prof;
21 | }
22 |
23 | public AgentItem(long agent, String name, String prof, int toughness, int healing, int condition) {
24 | this.agent = agent;
25 | this.name = name;
26 | this.prof = prof;
27 | this.toughness = toughness;
28 | this.healing = healing;
29 | this.condition = condition;
30 | }
31 |
32 | // Public Methods
33 | public String[] toStringArray() {
34 | String[] array = new String[9];
35 | array[0] = Long.toHexString(agent);
36 | array[1] = String.valueOf(instid);
37 | array[2] = String.valueOf(first_aware);
38 | array[3] = String.valueOf(last_aware);
39 | array[4] = name;
40 | array[5] = prof;
41 | array[6] = String.valueOf(toughness);
42 | array[7] = String.valueOf(healing);
43 | array[8] = String.valueOf(condition);
44 | return array;
45 | }
46 |
47 | // Getters
48 | public long getAgent() {
49 | return agent;
50 | }
51 |
52 | public int getInstid() {
53 | return instid;
54 | }
55 |
56 | public int getFirstAware() {
57 | return first_aware;
58 | }
59 |
60 | public int getLastAware() {
61 | return last_aware;
62 | }
63 |
64 | public String getName() {
65 | return name;
66 | }
67 |
68 | public String getProf() {
69 | return prof;
70 | }
71 |
72 | public int getToughness() {
73 | return toughness;
74 | }
75 |
76 | public int getHealing() {
77 | return healing;
78 | }
79 |
80 | public int getCondition() {
81 | return condition;
82 | }
83 |
84 | // Setters
85 | public void setInstid(int instid) {
86 | this.instid = instid;
87 | }
88 |
89 | public void setFirstAware(int first_aware) {
90 | this.first_aware = first_aware;
91 | }
92 |
93 | public void setLastAware(int last_aware) {
94 | this.last_aware = last_aware;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/data/BossData.java:
--------------------------------------------------------------------------------
1 | package data;
2 |
3 | public class BossData
4 | {
5 |
6 | // Fields
7 | private long agent = 0;
8 | private int instid = 0;
9 | private int first_aware = 0;
10 | private int last_aware = Integer.MAX_VALUE;
11 | private int id;
12 | private String name = "UNKNOWN";
13 | private int health = -1;
14 |
15 | // Constructors
16 | public BossData(int id)
17 | {
18 | this.id = id;
19 | }
20 |
21 | // Public Methods
22 | public String[] getPhaseNames()
23 | {
24 | if (name.equals("Vale Guardian"))
25 | {
26 | return new String[] { "100% - 66%", "66% - 33%", "33% - 0%" };
27 | }
28 | else if (name.equals("Gorseval the Multifarious"))
29 | {
30 | return new String[] { "100% - 66%", "66% - 33%", "33% - 0%" };
31 | }
32 | else if (name.equals("Sabetha the Saboteur"))
33 | {
34 | return new String[] { "100% - 75%", "75% - 50%", "50% - 25%", "25% - 0%" };
35 | }
36 | else if (name.equals("Slothasor"))
37 | {
38 | return new String[] { "100% - 80%", "80% - 60%", "60% - 40%", "40% - 20%", "20% - 10%", "10% - 0%" };
39 | }
40 | else if (name.equals("Matthias Gabrel"))
41 | {
42 | return new String[] { "100% - 80%", "80% - 60%", "60% - 40%", "40% - 0%" };
43 | }
44 | else if (name.equals("Keep Construct"))
45 | {
46 | return new String[] { "100% - 66%", "66% - 33%", "33% - 0%" };
47 | }
48 | else if (name.equals("Xera"))
49 | {
50 | return new String[] { "100% - 50%", "50% - 0%" };
51 | }
52 | else if (name.equals("Cairn the Indomitable"))
53 | {
54 | return new String[] { "100% - 75%", "75% - 50%", "50% - 25%", "25% - 0%" };
55 | }
56 | else if (name.equals("Mursaat Overseer"))
57 | {
58 | return new String[] { "100% - 75%", "75% - 50%", "50% - 25%", "25% - 0%" };
59 | }
60 | else if (name.equals("Samarog"))
61 | {
62 | return new String[] { "100% - 66%", "66% - 33%", "33% - 0%" };
63 | }
64 | else if (name.equals("Deimos"))
65 | {
66 | return new String[] { "100% - 75%", "75% - 50%", "50% - 25%", "25% - 10%" };
67 | }
68 | return new String[] { "100% - 0%" };
69 | }
70 |
71 | public String[] toStringArray()
72 | {
73 | String[] array = new String[7];
74 | array[0] = Long.toHexString(agent);
75 | array[1] = String.valueOf(instid);
76 | array[2] = String.valueOf(first_aware);
77 | array[3] = String.valueOf(last_aware);
78 | array[4] = String.valueOf(id);
79 | array[5] = name;
80 | array[6] = String.valueOf(health);
81 | return array;
82 | }
83 |
84 | // Getters
85 | public long getAgent()
86 | {
87 | return agent;
88 | }
89 |
90 | public int getInstid()
91 | {
92 | return instid;
93 | }
94 |
95 | public int getFirstAware()
96 | {
97 | return first_aware;
98 | }
99 |
100 | public int getLastAware()
101 | {
102 | return last_aware;
103 | }
104 |
105 | public int getID()
106 | {
107 | return id;
108 | }
109 |
110 | public String getName()
111 | {
112 | return name;
113 | }
114 |
115 | public int getHealth()
116 | {
117 | return health;
118 | }
119 |
120 | // Setters
121 | public void setAgent(long agent)
122 | {
123 | this.agent = agent;
124 | }
125 |
126 | public void setInstid(int instid)
127 | {
128 | this.instid = instid;
129 | }
130 |
131 | public void setFirstAware(int first_aware)
132 | {
133 | this.first_aware = first_aware;
134 | }
135 |
136 | public void setLastAware(int last_aware)
137 | {
138 | this.last_aware = last_aware;
139 | }
140 |
141 | public void setName(String name)
142 | {
143 | this.name = name;
144 | }
145 |
146 | public void setHealth(int health)
147 | {
148 | this.health = health;
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/src/data/CombatData.java:
--------------------------------------------------------------------------------
1 | package data;
2 |
3 | import java.awt.Point;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | import enums.StateChange;
8 |
9 | public class CombatData
10 | {
11 |
12 | // Fields
13 | private List combat_list;
14 |
15 | // Constructors
16 | public CombatData()
17 | {
18 | this.combat_list = new ArrayList();
19 | }
20 |
21 | // Public Methods
22 | public void addItem(CombatItem item)
23 | {
24 | combat_list.add(item);
25 | }
26 |
27 | public List getStates(int src_instid, StateChange change)
28 | {
29 | List states = new ArrayList();
30 | for (CombatItem c : combat_list)
31 | {
32 | if (c.getSrcInstid() == src_instid && c.isStateChange().equals(change))
33 | {
34 | states.add(new Point(c.getTime(), (int) c.getDstAgent()));
35 | }
36 | }
37 | return states;
38 | }
39 |
40 | public int getSkillCount(int src_instid, int skill_id)
41 | {
42 | int count = 0;
43 | for (CombatItem c : combat_list)
44 | {
45 | if (c.getSrcInstid() == src_instid && c.getSkillID() == skill_id)
46 | {
47 | count++;
48 | }
49 | }
50 | return count;
51 | }
52 |
53 | // Getters
54 | public List getCombatList()
55 | {
56 | return combat_list;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/data/CombatItem.java:
--------------------------------------------------------------------------------
1 | package data;
2 |
3 | import enums.Activation;
4 | import enums.BuffRemove;
5 | import enums.IFF;
6 | import enums.Result;
7 | import enums.StateChange;
8 |
9 | public class CombatItem
10 | {
11 |
12 | // Fields
13 | private int time;
14 | private long src_agent;
15 | private long dst_agent;
16 | private int value;
17 | private int buff_dmg;
18 | private int overstack_value;
19 | private int skill_id;
20 | private int src_instid;
21 | private int dst_instid;
22 | private int src_master_instid;
23 | private IFF iff;
24 | private int is_buff;
25 | private Result result;
26 | private Activation is_activation;
27 | private BuffRemove is_buffremove;
28 | private int is_ninety;
29 | private int is_fifty;
30 | private int is_moving;
31 | private StateChange is_statechange;
32 | private int is_flanking;
33 |
34 | // Constructor
35 | public CombatItem(int time, long src_agent, long dst_agent, int value, int buff_dmg, int overstack_value,
36 | int skill_id, int src_instid, int dst_instid, int src_master_instid, IFF iff, int buff, Result result,
37 | Activation is_activation, BuffRemove is_buffremove, int is_ninety, int is_fifty, int is_moving,
38 | StateChange is_statechange, int is_flanking)
39 | {
40 | this.time = time;
41 | this.src_agent = src_agent;
42 | this.dst_agent = dst_agent;
43 | this.value = value;
44 | this.buff_dmg = buff_dmg;
45 | this.overstack_value = overstack_value;
46 | this.skill_id = skill_id;
47 | this.src_instid = src_instid;
48 | this.dst_instid = dst_instid;
49 | this.src_master_instid = src_master_instid;
50 | this.iff = iff;
51 | this.is_buff = buff;
52 | this.result = result;
53 | this.is_activation = is_activation;
54 | this.is_buffremove = is_buffremove;
55 | this.is_ninety = is_ninety;
56 | this.is_fifty = is_fifty;
57 | this.is_moving = is_moving;
58 | this.is_statechange = is_statechange;
59 | this.is_flanking = is_flanking;
60 | }
61 |
62 | // Public Methods
63 | public String[] toStringArray()
64 | {
65 | String[] array = new String[20];
66 | array[0] = String.valueOf(time);
67 | array[1] = Long.toHexString(src_agent);
68 | array[2] = Long.toHexString(dst_agent);
69 | array[3] = String.valueOf(value);
70 | array[4] = String.valueOf(buff_dmg);
71 | array[5] = String.valueOf(overstack_value);
72 | array[6] = String.valueOf(skill_id);
73 | array[7] = String.valueOf(src_instid);
74 | array[8] = String.valueOf(dst_instid);
75 | array[9] = String.valueOf(src_master_instid);
76 | array[10] = String.valueOf(iff);
77 | array[11] = String.valueOf(is_buff);
78 | array[12] = String.valueOf(result);
79 | array[13] = String.valueOf(is_activation);
80 | array[14] = String.valueOf(is_buffremove);
81 | array[15] = String.valueOf(is_ninety);
82 | array[16] = String.valueOf(is_fifty);
83 | array[17] = String.valueOf(is_moving);
84 | array[18] = String.valueOf(is_statechange);
85 | array[19] = String.valueOf(is_flanking);
86 | return array;
87 | }
88 |
89 | // Getters
90 | public int getTime()
91 | {
92 | return time;
93 | }
94 |
95 | public long getSrcAgent()
96 | {
97 | return src_agent;
98 | }
99 |
100 | public long getDstAgent()
101 | {
102 | return dst_agent;
103 | }
104 |
105 | public int getValue()
106 | {
107 | return value;
108 | }
109 |
110 | public int getBuffDmg()
111 | {
112 | return buff_dmg;
113 | }
114 |
115 | public int getOverstackValue()
116 | {
117 | return overstack_value;
118 | }
119 |
120 | public int getSkillID()
121 | {
122 | return skill_id;
123 | }
124 |
125 | public int getSrcInstid()
126 | {
127 | return src_instid;
128 | }
129 |
130 | public int getDstInstid()
131 | {
132 | return dst_instid;
133 | }
134 |
135 | public int getSrcMasterInstid()
136 | {
137 | return src_master_instid;
138 | }
139 |
140 | public IFF getIFF()
141 | {
142 | return iff;
143 | }
144 |
145 | public int isBuff()
146 | {
147 | return is_buff;
148 | }
149 |
150 | public Result getResult()
151 | {
152 | return result;
153 | }
154 |
155 | public Activation isActivation()
156 | {
157 | return is_activation;
158 | }
159 |
160 | public BuffRemove isBuffremove()
161 | {
162 | return is_buffremove;
163 | }
164 |
165 | public int isNinety()
166 | {
167 | return is_ninety;
168 | }
169 |
170 | public int isFifty()
171 | {
172 | return is_fifty;
173 | }
174 |
175 | public int isMoving()
176 | {
177 | return is_moving;
178 | }
179 |
180 | public int isFlanking()
181 | {
182 | return is_flanking;
183 | }
184 |
185 | public StateChange isStateChange()
186 | {
187 | return is_statechange;
188 | }
189 |
190 | // Setters
191 | public void setSrcAgent(long src_agent)
192 | {
193 | this.src_agent = src_agent;
194 | }
195 |
196 | public void setDstAgent(long dst_agent)
197 | {
198 | this.dst_agent = dst_agent;
199 | }
200 |
201 | public void setSrcInstid(int src_instid)
202 | {
203 | this.src_instid = src_instid;
204 | }
205 |
206 | public void setDstInstid(int dst_instid)
207 | {
208 | this.dst_instid = dst_instid;
209 | }
210 |
211 | }
--------------------------------------------------------------------------------
/src/data/LogData.java:
--------------------------------------------------------------------------------
1 | package data;
2 |
3 | import java.text.SimpleDateFormat;
4 | import java.util.Date;
5 | import java.util.TimeZone;
6 |
7 | public class LogData
8 | {
9 |
10 | // Fields
11 | private String build_version;
12 | private String pov = "N/A";
13 | private String log_start = "yyyy-MM-dd HH:mm:ss z";
14 | private String log_end = "yyyy-MM-dd HH:mm:ss z";
15 | private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
16 |
17 | // Constructors
18 | public LogData(String build_version)
19 | {
20 | this.build_version = build_version;
21 | this.sdf.setTimeZone(TimeZone.getDefault());
22 | }
23 |
24 | // Public Methods
25 | public String[] toStringArray()
26 | {
27 | String[] array = new String[4];
28 | array[0] = String.valueOf(build_version);
29 | array[1] = String.valueOf(pov);
30 | array[2] = String.valueOf(log_start);
31 | array[3] = String.valueOf(log_end);
32 | return array;
33 | }
34 |
35 | // Getters
36 | public String getBuildVersion()
37 | {
38 | return build_version;
39 | }
40 |
41 | public String getPOV()
42 | {
43 | return pov;
44 | }
45 |
46 | public String getLogStart()
47 | {
48 | return log_start;
49 | }
50 |
51 | public String getLogEnd()
52 | {
53 | return log_end;
54 | }
55 |
56 | // Setters
57 | public void setPOV(String pov)
58 | {
59 | this.pov = pov.substring(0, pov.lastIndexOf('\0'));
60 | }
61 |
62 | public void setLogStart(int unix_seconds)
63 | {
64 | this.log_start = sdf.format(new Date(unix_seconds * 1000L));
65 | }
66 |
67 | public void setLogEnd(int unix_seconds)
68 | {
69 | this.log_end = sdf.format(new Date(unix_seconds * 1000L));
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/data/SkillData.java:
--------------------------------------------------------------------------------
1 | package data;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import enums.CustomSkill;
7 |
8 | public class SkillData
9 | {
10 |
11 | // Fields
12 | private List skill_list;
13 |
14 | // Constructors
15 | public SkillData()
16 | {
17 | this.skill_list = new ArrayList();
18 | }
19 |
20 | // Public Methods
21 | public void addItem(SkillItem item)
22 | {
23 | skill_list.add(item);
24 | }
25 |
26 | public String getName(int ID)
27 | {
28 |
29 | // Custom
30 | CustomSkill custom_skill = CustomSkill.getEnum(ID);
31 | if (custom_skill != null)
32 | {
33 | return custom_skill.name();
34 | }
35 |
36 | // Normal
37 | for (SkillItem s : skill_list)
38 | {
39 | if (s.getID() == ID)
40 | {
41 | return s.getName();
42 | }
43 | }
44 |
45 | // Unknown
46 | return "uid: " + String.valueOf(ID);
47 | }
48 |
49 | // Getters
50 | public List getSkillList()
51 | {
52 | return skill_list;
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/data/SkillItem.java:
--------------------------------------------------------------------------------
1 | package data;
2 |
3 | public class SkillItem {
4 |
5 | // Fields
6 | private int ID;
7 | private String name;
8 |
9 | // Constructor
10 | public SkillItem(int ID, String name) {
11 | this.ID = ID;
12 | this.name = name;
13 | }
14 |
15 | // Public Methods
16 | public String[] toStringArray() {
17 | String[] array = new String[2];
18 | array[0] = String.valueOf(ID);
19 | array[1] = String.valueOf(name);
20 | return array;
21 | }
22 |
23 | // Getters
24 | public int getID() {
25 | return ID;
26 | }
27 |
28 | public String getName() {
29 | return name;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/enums/Activation.java:
--------------------------------------------------------------------------------
1 | package enums;
2 |
3 | public enum Activation
4 | {
5 | // Constants
6 | NONE(0),
7 | NORMAL(1),
8 | QUICKNESS(2),
9 | CANCEL_FIRE(3),
10 | CANCEL_CANCEL(4),
11 | RESET(5);
12 |
13 | // Fields
14 | private int ID;
15 |
16 | // Constructors
17 | private Activation(int ID)
18 | {
19 | this.ID = ID;
20 | }
21 |
22 | // Public Methods
23 | public static Activation getEnum(int ID)
24 | {
25 | for (Activation a : values())
26 | {
27 | if (a.getID() == ID)
28 | {
29 | return a;
30 | }
31 | }
32 | return null;
33 | }
34 |
35 | // Getters
36 | public int getID()
37 | {
38 | return ID;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/enums/Agent.java:
--------------------------------------------------------------------------------
1 | package enums;
2 |
3 | public enum Agent
4 | {
5 |
6 | // Constants
7 | NPC(-1, "NPC"),
8 | GADGET(0, "GDG"),
9 | GUARDIAN(1, "Guardian"),
10 | WARRIOR(2, "Warrior"),
11 | ENGINEER(3, "Engineer"),
12 | RANGER(4, "Ranger"),
13 | THIEF(5, "Thief"),
14 | ELEMENTALIST(6, "Elementalist"),
15 | MESMER(7, "Mesmer"),
16 | NECROMANCER(8, "Necromancer"),
17 | REVENANT(9, "Revenant"),
18 | DRAGONHUNTER(10, "Dragonhunter"),
19 | BERSERKER(11, "Berserker"),
20 | SCRAPPER(12, "Scrapper"),
21 | DRUID(13, "Druid"),
22 | DAREDEVIL(14, "Daredevil"),
23 | TEMPEST(15, "Tempest"),
24 | CHRONOMANCER(16, "Chronomancer"),
25 | REAPER(17, "Reaper"),
26 | HERALD(18, "Herald");
27 |
28 | // Fields
29 | private String name;
30 | private int ID;
31 |
32 | // Constructor
33 | Agent(int ID, String name)
34 | {
35 | this.name = name;
36 | this.ID = ID;
37 | }
38 |
39 | // Public Methods
40 | public static Agent getEnum(int ID, int is_elite)
41 | {
42 | for (Agent p : values())
43 | {
44 | if (is_elite == -1)
45 | {
46 | if ((ID & 0xffff0000) == 0xffff0000)
47 | {
48 | return Agent.GADGET;
49 | }
50 | else
51 | {
52 | return Agent.NPC;
53 | }
54 | }
55 | else if (is_elite == 0)
56 | {
57 | if (p.getID() == ID)
58 | {
59 | return p;
60 | }
61 | }
62 | else if (is_elite == 1)
63 | {
64 | if (p.getID() == ID + 9)
65 | {
66 | return p;
67 | }
68 | }
69 | }
70 | return null;
71 | }
72 |
73 | // Getters
74 | public String getName()
75 | {
76 | return name;
77 | }
78 |
79 | public int getID()
80 | {
81 | return ID;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/enums/Boon.java:
--------------------------------------------------------------------------------
1 | package enums;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public enum Boon
7 | {
8 | // Boon
9 | MIGHT("Might", "MGHT", "intensity", 25),
10 | QUICKNESS("Quickness", "QCKN", "duration", 5),
11 | FURY("Fury", "FURY", "duration", 9),
12 | PROTECTION("Protection", "PROT", "duration", 5),
13 |
14 | // Mesmer (also Ventari Revenant o.o)
15 | ALACRITY("Alacrity", "ALAC", "duration", 9),
16 |
17 | // Ranger
18 | SPOTTER("Spotter", "SPOT", "duration", 1),
19 | SPIRIT_OF_FROST("Spirit of Frost", "FRST", "duration", 1),
20 | SUN_SPIRIT("Sun Spirit", "SUNS", "duration", 1),
21 | STONE_SPIRIT("Stone Spirit", "STNE", "duration", 1),
22 | STORM_SPIRIT("Storm Spirit", "STRM", "duration", 1),
23 | GLYPH_OF_EMPOWERMENT("Glyph of Empowerment", "GOFE", "duration", 1),
24 | GRACE_OF_THE_LAND("Grace of the Land", "GOTL", "intensity", 5),
25 |
26 | // Warrior
27 | EMPOWER_ALLIES("Empower Allies", "EALL", "duration", 1),
28 | BANNER_OF_STRENGTH("Banner of Strength", "STRB", "duration", 1),
29 | BANNER_OF_DISCIPLINE("Banner of Discipline", "DISC", "duration", 1),
30 | BANNER_OF_TACTICS("Banner of Tactics", "TACT", "duration", 1),
31 | BANNER_OF_DEFENCE("Banner of Defence", "DEFN", "duration", 1),
32 |
33 | // Revenant
34 | ASSASSINS_PRESENCE("Assassin's Presence", "ASNP", "duration", 1),
35 | NATURALISTIC_RESONANCE("Naturalistic Resonance", "NATR", "duration", 1),
36 |
37 | // Engineer
38 | PINPOINT_PRECISION("Pinpoint Distribution", "PIND", "duration", 1),
39 |
40 | // Elementalist
41 | SOOTHING_MIST("Soothing Mist", "MIST", "duration", 1),
42 |
43 | // Necro
44 | VAMPIRIC_PRESENCE("Vampiric Presence", "VAMP", "duration", 1),
45 |
46 | // Thief
47 | LEAD_ATTACKS("Lead Attacks", "LEAD", "intensity", 15),
48 | LOTUS_TRAINING("Lotus Training", "LOTS", "duration", 1),
49 | BOUNDING_DODGER("Bounding Dodger", "BDOG", "duration", 1),
50 |
51 | // Equipment
52 | MASTERFUL_CONCENTRATION("Masterful Concentration", "CONC", "duration", 1);
53 | // THORNS_RUNE("Thorns", "THRN", "intensity", 5);
54 |
55 | // Fields
56 | private String name;
57 | private String abrv;
58 | private String type;
59 | private int capacity;
60 |
61 | // Constructor
62 | private Boon(String name, String abrv, String type, int capacity)
63 | {
64 | this.name = name;
65 | this.abrv = abrv;
66 | this.type = type;
67 | this.capacity = capacity;
68 | }
69 |
70 | // Public Methods
71 | public static Boon getEnum(String name)
72 | {
73 | for (Boon b : values())
74 | {
75 | if (b.getName() == name)
76 | {
77 | return b;
78 | }
79 | }
80 | return null;
81 | }
82 |
83 | public static String[] getArray()
84 | {
85 | List boonList = new ArrayList();
86 | for (Boon b : values())
87 | {
88 | boonList.add(b.getAbrv());
89 | }
90 | return boonList.toArray(new String[boonList.size()]);
91 | }
92 |
93 | public static List getList()
94 | {
95 | List boonList = new ArrayList();
96 | for (Boon b : values())
97 | {
98 | boonList.add(b.getName());
99 | }
100 | return boonList;
101 | }
102 |
103 | // Getters
104 | public String getName()
105 | {
106 | return this.name;
107 | }
108 |
109 | public String getAbrv()
110 | {
111 | return this.abrv;
112 | }
113 |
114 | public String getType()
115 | {
116 | return this.type;
117 | }
118 |
119 | public int getCapacity()
120 | {
121 | return this.capacity;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/enums/BuffRemove.java:
--------------------------------------------------------------------------------
1 | package enums;
2 |
3 | public enum BuffRemove
4 | {
5 | // Constants
6 | NONE(0),
7 | ALL(1),
8 | SINGLE(2),
9 | MANUAL(3);
10 |
11 | // Fields
12 | private int ID;
13 |
14 | // Constructors
15 | private BuffRemove(int ID)
16 | {
17 | this.ID = ID;
18 | }
19 |
20 | // Public Methods
21 | public static BuffRemove getEnum(int ID)
22 | {
23 | for (BuffRemove b : values())
24 | {
25 | if (b.getID() == ID)
26 | {
27 | return b;
28 | }
29 | }
30 | return null;
31 | }
32 |
33 | // Getters
34 | public int getID()
35 | {
36 | return ID;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/enums/CustomSkill.java:
--------------------------------------------------------------------------------
1 | package enums;
2 |
3 | public enum CustomSkill
4 | {
5 |
6 | // Constants
7 | RESURRECT(1066, "Resurrect"),
8 | BANDAGE(1175, "Bandage"),
9 | DODGE(65001, "Dodge");
10 |
11 | // Fields
12 | private int ID;
13 | private String name;
14 |
15 | // Constructors
16 | private CustomSkill(int ID, String name)
17 | {
18 | this.ID = ID;
19 | this.name = name;
20 | }
21 |
22 | // Public Methods
23 | public static CustomSkill getEnum(int ID)
24 | {
25 | for (CustomSkill c : values())
26 | {
27 | if (c.getID() == ID)
28 | {
29 | return c;
30 | }
31 | }
32 | return null;
33 | }
34 |
35 | // Getters
36 | public int getID()
37 | {
38 | return ID;
39 | }
40 |
41 | public String getName()
42 | {
43 | return name;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/enums/IFF.java:
--------------------------------------------------------------------------------
1 | package enums;
2 |
3 | public enum IFF {
4 |
5 | // Constants
6 | FRIEND(0),
7 | FOE(1),
8 | UNKNOWN(2);
9 |
10 | // Fields
11 | private int ID;
12 |
13 | // Constructors
14 | private IFF(int ID) {
15 | this.ID = ID;
16 | }
17 |
18 | // Public Methods
19 | public static IFF getEnum(int ID) {
20 | for (IFF i : values()) {
21 | if (i.getID() == ID) {
22 | return i;
23 | }
24 | }
25 | return null;
26 | }
27 |
28 | // Getters
29 | public int getID() {
30 | return ID;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/enums/MenuChoice.java:
--------------------------------------------------------------------------------
1 | package enums;
2 |
3 | public enum MenuChoice {
4 |
5 | // Constants
6 | DUMP_EVTC(0, false),
7 | FINAL_DPS(1, true),
8 | PHASE_DPS(2, true),
9 | DMG_DIST(3, true),
10 | G_TOTAL_DMG(4, false),
11 | MISC_STATS(5, true),
12 | FINAL_BOONS(6, true),
13 | PHASE_BOONS(7, true),
14 | DUMP_TABLES(8, false),
15 | QUIT(9, false);
16 |
17 | // Fields
18 | private int ID;
19 | private boolean can_be_associated;
20 |
21 | // Constructor
22 | MenuChoice(int ID, boolean can_be_associated) {
23 | this.ID = ID;
24 | this.can_be_associated = can_be_associated;
25 | }
26 |
27 | // Public Methods
28 | public static MenuChoice getEnum(int ID) {
29 | for (MenuChoice c : values()) {
30 | if (c.getID() == ID) {
31 | return c;
32 | }
33 | }
34 | return null;
35 | }
36 |
37 | // Getters
38 | public int getID() {
39 | return ID;
40 | }
41 |
42 | public boolean canBeAssociated() {
43 | return can_be_associated;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/enums/Result.java:
--------------------------------------------------------------------------------
1 | package enums;
2 |
3 | public enum Result {
4 |
5 | // Constants
6 | NORMAL(0),
7 | CRIT(1),
8 | GLANCE(2),
9 | BLOCK(3),
10 | EVADE(4),
11 | INTERRUPT(5),
12 | ABSORB(6),
13 | BLIND(7),
14 | KILLING_BLOW(8);
15 |
16 | // Fields
17 | private int ID;
18 |
19 | // Constructors
20 | private Result(int ID) {
21 | this.ID = ID;
22 | }
23 |
24 | // Public Methods
25 | public static Result getEnum(int ID) {
26 | for (Result r : values()) {
27 | if (r.getID() == ID) {
28 | return r;
29 | }
30 | }
31 | return null;
32 | }
33 |
34 | // Getters
35 | public int getID() {
36 | return ID;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/enums/StateChange.java:
--------------------------------------------------------------------------------
1 | package enums;
2 |
3 | public enum StateChange
4 | {
5 |
6 | // Constants
7 | NORMAL(0),
8 | ENTER_COMBAT(1),
9 | EXIT_COMBAT(2),
10 | CHANGE_UP(3),
11 | CHANGE_DEAD(4),
12 | CHANGE_DOWN(5),
13 | SPAWN(6),
14 | DESPAWN(7),
15 | HEALTH_UPDATE(8),
16 | LOG_START(9),
17 | LOG_END(10),
18 | WEAPON_SWAP(11),
19 | MAX_HEALTH_UPDATE(12),
20 | POINT_OF_VIEW(13),
21 | CBTS_LANGUAGE(14);
22 |
23 | // Fields
24 | private int ID;
25 |
26 | // Constructors
27 | private StateChange(int ID)
28 | {
29 | this.ID = ID;
30 | }
31 |
32 | // Public Methods
33 | public static StateChange getEnum(int ID)
34 | {
35 | for (StateChange s : values())
36 | {
37 | if (s.getID() == ID)
38 | {
39 | return s;
40 | }
41 | }
42 | return null;
43 | }
44 |
45 | // Getters
46 | public int getID()
47 | {
48 | return ID;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/player/BoonLog.java:
--------------------------------------------------------------------------------
1 | package player;
2 |
3 | public class BoonLog {
4 |
5 | // Fields
6 | private int time = 0;
7 | private int value = 0;
8 |
9 | // Constructor
10 | public BoonLog(int time, int value) {
11 | this.time = time;
12 | this.value = value;
13 | }
14 |
15 | // Getters
16 | public int getTime() {
17 | return time;
18 | }
19 |
20 | public int getValue() {
21 | return value;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/player/DamageLog.java:
--------------------------------------------------------------------------------
1 | package player;
2 |
3 | import enums.Result;
4 |
5 | public class DamageLog
6 | {
7 |
8 | // Fields
9 | private int time;
10 | private int damage;
11 | private int skill_id;
12 | private int buff;
13 | private Result result;
14 | private int is_ninety;
15 | private int is_moving;
16 | private int is_flanking;
17 |
18 | // Constructor
19 | public DamageLog(int time, int damage, int skill_id, int buff, Result result, int is_ninety, int is_moving,
20 | int is_flanking)
21 | {
22 | this.time = time;
23 | this.damage = damage;
24 | this.skill_id = skill_id;
25 | this.buff = buff;
26 | this.result = result;
27 | this.is_ninety = is_ninety;
28 | this.is_moving = is_moving;
29 | this.is_flanking = is_flanking;
30 | }
31 |
32 | // Getters
33 | public int getTime()
34 | {
35 | return time;
36 | }
37 |
38 | public int getDamage()
39 | {
40 | return damage;
41 | }
42 |
43 | public int getID()
44 | {
45 | return skill_id;
46 | }
47 |
48 | public int isCondi()
49 | {
50 | return buff;
51 | }
52 |
53 | public Result getResult()
54 | {
55 | return result;
56 | }
57 |
58 | public int isNinety()
59 | {
60 | return is_ninety;
61 | }
62 |
63 | public int isMoving()
64 | {
65 | return is_moving;
66 | }
67 |
68 | public int isFlanking()
69 | {
70 | return is_flanking;
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/player/Player.java:
--------------------------------------------------------------------------------
1 | package player;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | import data.AgentItem;
9 | import data.BossData;
10 | import data.CombatItem;
11 | import data.SkillData;
12 | import enums.Boon;
13 | import enums.IFF;
14 | import enums.StateChange;
15 | import statistics.Statistics;
16 |
17 | public class Player
18 | {
19 | // Fields
20 | private int instid;
21 | private String account;
22 | private String character;
23 | private String group;
24 | private String prof;
25 | private int toughness;
26 | private int healing;
27 | private int condition;
28 | private List damage_logs = new ArrayList();
29 | private Map> boon_map = new HashMap<>();
30 |
31 | // Constructors
32 | public Player(AgentItem agent)
33 | {
34 | this.instid = agent.getInstid();
35 | String[] name = agent.getName().split(Character.toString('\0'));
36 | this.character = name[0];
37 | this.account = name[1];
38 | this.group = name[2];
39 | if (Statistics.hiding_players)
40 | {
41 | this.character = "P:" + String.format("%04d", instid);
42 | this.account = ":A." + String.format("%04d", instid);
43 | }
44 | this.prof = agent.getProf();
45 | this.toughness = agent.getToughness();
46 | this.healing = agent.getHealing();
47 | this.condition = agent.getCondition();
48 | }
49 |
50 | // Getters
51 | public int getInstid()
52 | {
53 | return instid;
54 | }
55 |
56 | public String getAccount()
57 | {
58 | return account;
59 | }
60 |
61 | public String getCharacter()
62 | {
63 | return character;
64 | }
65 |
66 | public String getGroup()
67 | {
68 | return group;
69 | }
70 |
71 | public String getProf()
72 | {
73 | return prof;
74 | }
75 |
76 | public int getToughness()
77 | {
78 | return toughness;
79 | }
80 |
81 | public int getHealing()
82 | {
83 | return healing;
84 | }
85 |
86 | public int getCondition()
87 | {
88 | return condition;
89 | }
90 |
91 | public List getDamageLogs(BossData bossData, List combatList)
92 | {
93 | if (damage_logs.isEmpty())
94 | {
95 | setDamageLogs(bossData, combatList);
96 | }
97 | return damage_logs;
98 | }
99 |
100 | public Map> getBoonMap(BossData bossData, SkillData skillData, List combatList)
101 | {
102 | if (boon_map.isEmpty())
103 | {
104 | setBoonMap(bossData, skillData, combatList);
105 | }
106 | return boon_map;
107 | }
108 |
109 | // Private Methods
110 | private void setDamageLogs(BossData bossData, List combatList)
111 | {
112 | int time_start = bossData.getFirstAware();
113 | for (CombatItem c : combatList)
114 | {
115 | if (instid == c.getSrcInstid() || instid == c.getSrcMasterInstid())
116 | {
117 | StateChange state = c.isStateChange();
118 | int time = c.getTime() - time_start;
119 | if (bossData.getInstid() == c.getDstInstid() && c.getIFF().equals(IFF.FOE))
120 | {
121 | if (state.equals(StateChange.NORMAL))
122 | {
123 | if (c.isBuff() == 1 && c.getBuffDmg() != 0)
124 | {
125 | damage_logs.add(new DamageLog(time, c.getBuffDmg(), c.getSkillID(), c.isBuff(),
126 | c.getResult(), c.isNinety(), c.isMoving(), c.isFlanking()));
127 | }
128 | else if (c.isBuff() == 0 && c.getValue() != 0)
129 | {
130 | damage_logs.add(new DamageLog(time, c.getValue(), c.getSkillID(), c.isBuff(),
131 | c.getResult(), c.isNinety(), c.isMoving(), c.isFlanking()));
132 | }
133 | }
134 | }
135 | }
136 | }
137 | }
138 |
139 | public void setBoonMap(BossData bossData, SkillData skillData, List combatList)
140 | {
141 |
142 | // Initialize Boon Map with every Boon
143 | for (Boon boon : Boon.values())
144 | {
145 | boon_map.put(boon.getName(), new ArrayList());
146 | }
147 |
148 | // Fill in Boon Map
149 | int time_start = bossData.getFirstAware();
150 | int fight_duration = bossData.getLastAware() - time_start;
151 | for (CombatItem c : combatList)
152 | {
153 | if (instid == c.getDstInstid())
154 | {
155 | String skill_name = skillData.getName(c.getSkillID());
156 | if (c.isBuff() == 1 && c.getValue() > 0)
157 | {
158 | if (boon_map.containsKey(skill_name))
159 | {
160 | int time = c.getTime() - time_start;
161 | if (time < fight_duration)
162 | {
163 | boon_map.get(skill_name).add(new BoonLog(time, c.getValue()));
164 | }
165 | else
166 | {
167 | break;
168 | }
169 | }
170 | }
171 | }
172 | }
173 |
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/src/statistics/Parse.java:
--------------------------------------------------------------------------------
1 | package statistics;
2 |
3 | import java.io.BufferedInputStream;
4 | import java.io.File;
5 | import java.io.FileInputStream;
6 | import java.io.IOException;
7 | import java.io.UnsupportedEncodingException;
8 | import java.nio.ByteBuffer;
9 | import java.nio.ByteOrder;
10 | import java.util.List;
11 | import java.util.Scanner;
12 | import java.util.zip.ZipEntry;
13 | import java.util.zip.ZipFile;
14 |
15 | import data.AgentData;
16 | import data.AgentItem;
17 | import data.BossData;
18 | import data.CombatData;
19 | import data.CombatItem;
20 | import data.LogData;
21 | import data.SkillData;
22 | import data.SkillItem;
23 | import enums.Activation;
24 | import enums.Agent;
25 | import enums.BuffRemove;
26 | import enums.IFF;
27 | import enums.Result;
28 | import enums.StateChange;
29 | import utility.TableBuilder;
30 | import utility.Utility;
31 |
32 | public class Parse
33 | {
34 | // Fields
35 | private BufferedInputStream f = null;
36 | private LogData log_data;
37 | private BossData boss_data;
38 | private AgentData agent_data = new AgentData();
39 | private SkillData skill_data = new SkillData();
40 | private CombatData combat_data = new CombatData();
41 |
42 | // Constructor
43 | public Parse(String file_path) throws IOException
44 | {
45 |
46 | // Read .evtc file into buffered input stream
47 | ZipFile zip_file = null;
48 | if (file_path.endsWith(".zip"))
49 | {
50 | zip_file = new ZipFile(file_path);
51 | ZipEntry evtc_file = zip_file.entries().nextElement();
52 | f = new BufferedInputStream(zip_file.getInputStream(evtc_file));
53 | }
54 | else if (file_path.endsWith(".evtc"))
55 | {
56 | f = new BufferedInputStream(new FileInputStream(new File(file_path)));
57 | }
58 |
59 | // Parse file
60 | try
61 | {
62 | parseBossData();
63 | parseAgentData();
64 | parseSkillData();
65 | parseCombatList();
66 | fillMissingData();
67 | }
68 |
69 | // Close streams
70 | finally
71 | {
72 | try
73 | {
74 | if (zip_file != null)
75 | {
76 | zip_file.close();
77 | }
78 | f.close();
79 | } catch (IOException e)
80 | {
81 | e.printStackTrace();
82 | }
83 | }
84 | }
85 |
86 | // Public Methods
87 | public LogData getLogData()
88 | {
89 | return log_data;
90 | }
91 |
92 | public BossData getBossData()
93 | {
94 | return boss_data;
95 | }
96 |
97 | public AgentData getAgentData()
98 | {
99 | return agent_data;
100 | }
101 |
102 | public SkillData getSkillData()
103 | {
104 | return skill_data;
105 | }
106 |
107 | public CombatData getCombatData()
108 | {
109 | return combat_data;
110 | }
111 |
112 | // Private Methods
113 | private void parseBossData() throws IOException
114 | {
115 | // 12 bytes: arc build version
116 | String build_version = getString(12);
117 | this.log_data = new LogData(build_version);
118 |
119 | // 1 byte: skip
120 | safeSkip(1);
121 |
122 | // 2 bytes: boss instance ID
123 | int instid = getShort();
124 |
125 | // 1 byte: position
126 | safeSkip(1);
127 |
128 | // BossData
129 | this.boss_data = new BossData(instid);
130 | }
131 |
132 | private void parseAgentData() throws IOException
133 | {
134 | // 4 bytes: player count
135 | int player_count = getInt();
136 |
137 | // 96 bytes: each player
138 | for (int i = 0; i < player_count; i++)
139 | {
140 | // 8 bytes: agent
141 | long agent = getLong();
142 |
143 | // 4 bytes: profession
144 | int prof = getInt();
145 |
146 | // 4 bytes: is_elite
147 | int is_elite = getInt();
148 |
149 | // 4 bytes: toughness
150 | int toughness = getInt();
151 |
152 | // 4 bytes: healing
153 | int healing = getInt();
154 |
155 | // 4 bytes: condition
156 | int condition = getInt();
157 |
158 | // 68 bytes: name
159 | String name = getString(68);
160 |
161 | // Agent
162 | Agent a = Agent.getEnum(prof, is_elite);
163 |
164 | // Add an agent
165 | if (a != null)
166 | {
167 | // NPC
168 | if (a.equals(Agent.NPC))
169 | {
170 | agent_data.addItem(a, new AgentItem(agent, name, a.getName() + ":" + String.format("%05d", prof)));
171 | }
172 | // Gadget
173 | else if (a.equals(Agent.GADGET))
174 | {
175 | agent_data.addItem(a,
176 | new AgentItem(agent, name, a.getName() + ":" + String.format("%05d", prof & 0x0000ffff)));
177 | }
178 | // Player
179 | else
180 | {
181 | agent_data.addItem(a, new AgentItem(agent, name, a.getName(), toughness, healing, condition));
182 | }
183 | }
184 | // Unknown
185 | else
186 | {
187 | agent_data.addItem(a, new AgentItem(agent, name, String.valueOf(prof), toughness, healing, condition));
188 | }
189 | }
190 | }
191 |
192 | private void parseSkillData() throws IOException
193 | {
194 | // 4 bytes: player count
195 | int skill_count = getInt();
196 |
197 | // 68 bytes: each skill
198 | for (int i = 0; i < skill_count; i++)
199 | {
200 | // 4 bytes: skill ID
201 | int skill_id = getInt();
202 |
203 | // 64 bytes: name
204 | String name = getString(64);
205 |
206 | // Add skill
207 | skill_data.addItem(new SkillItem(skill_id, name));
208 | }
209 | }
210 |
211 | private void parseCombatList() throws IOException
212 | {
213 | // 64 bytes: each combat
214 | while (f.available() >= 64)
215 | {
216 | // 8 bytes: time
217 | int time = (int) getLong();
218 |
219 | // 8 bytes: src_agent
220 | long src_agent = getLong();
221 |
222 | // 8 bytes: dst_agent
223 | long dst_agent = getLong();
224 |
225 | // 4 bytes: value
226 | int value = getInt();
227 |
228 | // 4 bytes: buff_dmg
229 | int buff_dmg = getInt();
230 |
231 | // 2 bytes: overstack_value
232 | int overstack_value = getShort();
233 |
234 | // 2 bytes: skill_id
235 | int skill_id = getShort();
236 |
237 | // 2 bytes: src_instid
238 | int src_instid = getShort();
239 |
240 | // 2 bytes: dst_instid
241 | int dst_instid = getShort();
242 |
243 | // 2 bytes: src_master_instid
244 | int src_master_instid = getShort();
245 |
246 | // 9 bytes: garbage
247 | safeSkip(9);
248 |
249 | // 1 byte: iff
250 | IFF iff = IFF.getEnum(f.read());
251 |
252 | // 1 byte: buff
253 | int buff = f.read();
254 |
255 | // 1 byte: result
256 | Result result = Result.getEnum(f.read());
257 |
258 | // 1 byte: is_activation
259 | Activation is_activation = Activation.getEnum(f.read());
260 |
261 | // 1 byte: is_buffremove
262 | BuffRemove is_buffremove = BuffRemove.getEnum(f.read());
263 |
264 | // 1 byte: is_ninety
265 | int is_ninety = f.read();
266 |
267 | // 1 byte: is_fifty
268 | int is_fifty = f.read();
269 |
270 | // 1 byte: is_moving
271 | int is_moving = f.read();
272 |
273 | // 1 byte: is_statechange
274 | StateChange is_statechange = StateChange.getEnum(f.read());
275 |
276 | // 1 byte: is_flanking
277 | int is_flanking = f.read();
278 |
279 | // 3 bytes: garbage
280 | safeSkip(3);
281 |
282 | // Add combat
283 | combat_data.addItem(new CombatItem(time, src_agent, dst_agent, value, buff_dmg, overstack_value, skill_id,
284 | src_instid, dst_instid, src_master_instid, iff, buff, result, is_activation, is_buffremove,
285 | is_ninety, is_fifty, is_moving, is_statechange, is_flanking));
286 | }
287 | }
288 |
289 | public void fillMissingData()
290 | {
291 | // Set Agent instid, first_aware and last_aware
292 | List player_list = agent_data.getPlayerAgentList();
293 | List agent_list = agent_data.getAllAgentsList();
294 | List combat_list = combat_data.getCombatList();
295 | for (AgentItem a : agent_list)
296 | {
297 | boolean assigned_first = false;
298 | for (CombatItem c : combat_list)
299 | {
300 | if (a.getAgent() == c.getSrcAgent() && c.getSrcInstid() != 0)
301 | {
302 | if (!assigned_first)
303 | {
304 | a.setInstid(c.getSrcInstid());
305 | a.setFirstAware(c.getTime());
306 | assigned_first = true;
307 | }
308 | a.setLastAware(c.getTime());
309 | }
310 | else if (a.getAgent() == c.getDstAgent() && c.getDstInstid() != 0)
311 | {
312 | if (!assigned_first)
313 | {
314 | a.setInstid(c.getDstInstid());
315 | a.setFirstAware(c.getTime());
316 | assigned_first = true;
317 | }
318 | a.setLastAware(c.getTime());
319 | }
320 | else if (c.isStateChange() == StateChange.POINT_OF_VIEW)
321 | {
322 | int pov_instid = c.getSrcInstid();
323 | for (AgentItem p : player_list)
324 | {
325 | if (pov_instid == p.getInstid())
326 | {
327 | log_data.setPOV(p.getName());
328 | }
329 | }
330 |
331 | }
332 | else if (c.isStateChange() == StateChange.LOG_START)
333 | {
334 | log_data.setLogStart(c.getValue());
335 | }
336 | else if (c.isStateChange() == StateChange.LOG_END)
337 | {
338 | log_data.setLogEnd(c.getValue());
339 | }
340 | }
341 | }
342 |
343 | // Manual log target selection
344 | if (boss_data.getID() == 1)
345 | {
346 | targetSelection();
347 | }
348 |
349 | // Set Boss data agent, instid, first_aware, last_aware and name
350 | List NPC_list = agent_data.getNPCAgentList();
351 | for (AgentItem NPC : NPC_list)
352 | {
353 | if (NPC.getProf().endsWith(String.valueOf(boss_data.getID())))
354 | {
355 | if (boss_data.getAgent() == 0)
356 | {
357 | boss_data.setAgent(NPC.getAgent());
358 | boss_data.setInstid(NPC.getInstid());
359 | boss_data.setFirstAware(NPC.getFirstAware());
360 | boss_data.setName(NPC.getName());
361 | }
362 | boss_data.setLastAware(NPC.getLastAware());
363 | }
364 | }
365 |
366 | // Set Boss health
367 | for (CombatItem c : combat_list)
368 | {
369 | if (c.getSrcInstid() == boss_data.getInstid() && c.isStateChange().equals(StateChange.MAX_HEALTH_UPDATE))
370 | {
371 | boss_data.setHealth((int) c.getDstAgent());
372 | break;
373 | }
374 | }
375 |
376 | // Dealing with second half of Xera | ((22611300 * 0.5) + (25560600 *
377 | // 0.5)
378 | int xera_2_instid = 0;
379 | for (AgentItem NPC : NPC_list)
380 | {
381 | if (NPC.getProf().contains("16286"))
382 | {
383 | xera_2_instid = NPC.getInstid();
384 | boss_data.setHealth(24085950);
385 | boss_data.setLastAware(NPC.getLastAware());
386 | for (CombatItem c : combat_list)
387 | {
388 | if (c.getSrcInstid() == xera_2_instid)
389 | {
390 | c.setSrcInstid(boss_data.getInstid());
391 | }
392 | if (c.getDstInstid() == xera_2_instid)
393 | {
394 | c.setDstInstid(boss_data.getInstid());
395 | }
396 | }
397 | break;
398 | }
399 | }
400 |
401 | }
402 |
403 | @SuppressWarnings("resource")
404 | private void targetSelection()
405 | {
406 | List NPC_list = agent_data.getNPCAgentList();
407 | TableBuilder target_table = new TableBuilder();
408 | target_table.addTitle("NPC List");
409 | target_table.addRow("ID", "Name", "Species");
410 |
411 | for (AgentItem NPC : NPC_list)
412 | {
413 | target_table.addRow(String.valueOf(NPC.getInstid()), NPC.getName(), NPC.getProf().substring(4));
414 | }
415 |
416 | System.out.println(target_table.toString());
417 |
418 | // Read user input
419 | Scanner scan = null;
420 | scan = new Scanner(System.in);
421 | boolean quitting = false;
422 | while (!quitting)
423 | {
424 | System.out.println(Utility.boxText("Select an NPC to target by ID"));
425 | System.out.print(" >> ");
426 | // A number
427 | if (scan.hasNextInt())
428 | {
429 | int target_id = scan.nextInt();
430 | for (AgentItem NPC : NPC_list)
431 | {
432 | // Input matches an ID
433 | if (target_id == NPC.getInstid())
434 | {
435 | boss_data.setAgent(NPC.getAgent());
436 | boss_data.setInstid(NPC.getInstid());
437 | boss_data.setFirstAware(NPC.getFirstAware());
438 | boss_data.setName(NPC.getName());
439 | boss_data.setLastAware(NPC.getLastAware());
440 | quitting = true;
441 | break;
442 | }
443 | }
444 | if (!quitting)
445 | {
446 | System.out.println(Utility.boxText("WARNING : Invalid NPC ID"));
447 | }
448 | }
449 | else
450 | {
451 | System.out.println(Utility.boxText("WARNING : Invalid NPC ID"));
452 | }
453 | scan.nextLine();
454 | }
455 | }
456 |
457 | // Override
458 | @Override
459 | public String toString()
460 | {
461 | // Build tables
462 | StringBuilder output = new StringBuilder();
463 | TableBuilder table = new TableBuilder();
464 |
465 | // Log Data Table
466 | table.addTitle("LOG DATA");
467 | table.addRow("build_version", "point_of_view", "log_start", "log_end");
468 | table.addRow(log_data.toStringArray());
469 | output.append(table.toString() + System.lineSeparator());
470 | table.clear();
471 |
472 | // Boss Data Table
473 | table.addTitle("BOSS DATA");
474 | table.addRow("agent", "instid", "first_aware", "last_aware", "id", "name", "health");
475 | table.addRow(boss_data.toStringArray());
476 | output.append(table.toString() + System.lineSeparator());
477 | table.clear();
478 |
479 | // Player Data
480 | List playerAgents = agent_data.getPlayerAgentList();
481 | List NPCAgents = agent_data.getNPCAgentList();
482 | List gadgetAgents = agent_data.getGadgetAgentList();
483 | table.addTitle("AGENT DATA");
484 | table.addRow("agent", "instid", "first_aware", "last_aware", "name", "prof", "toughness", "healing",
485 | "condition");
486 | for (AgentItem player : playerAgents)
487 | {
488 | table.addRow(player.toStringArray());
489 | }
490 | for (AgentItem npc : NPCAgents)
491 | {
492 | table.addRow(npc.toStringArray());
493 | }
494 | for (AgentItem gadget : gadgetAgents)
495 | {
496 | table.addRow(gadget.toStringArray());
497 | }
498 | output.append(table.toString() + System.lineSeparator());
499 | table.clear();
500 |
501 | // Skill Data
502 | List skillList = skill_data.getSkillList();
503 | table.addTitle("SKILL DATA");
504 | table.addRow("ID", "name");
505 | for (SkillItem s : skillList)
506 | {
507 | table.addRow(s.toStringArray());
508 | }
509 | output.append(table.toString() + System.lineSeparator());
510 | table.clear();
511 |
512 | // Combat Data Table
513 | List combatList = combat_data.getCombatList();
514 | table.addTitle("COMBAT DATA");
515 | table.addRow("time", "src_agent", "dst_agent", "value", "buff_dmg", "overstack_value", "skill_id", "src_instid",
516 | "dst_instid", "src_master_instid", "iff", "buff", "is_crit", "is_activation", "is_buffremove",
517 | "is_ninety", "is_fifty", "is_moving", "is_statechange", "is_flanking");
518 | for (CombatItem c : combatList)
519 | {
520 | table.addRow(c.toStringArray());
521 | }
522 | output.append(table.toString() + System.lineSeparator());
523 |
524 | return output.toString();
525 | }
526 |
527 | // Private Methods
528 | private void safeSkip(long bytes_to_skip) throws IOException
529 | {
530 | while (bytes_to_skip > 0)
531 | {
532 | long bytes_actually_skipped = f.skip(bytes_to_skip);
533 | if (bytes_actually_skipped > 0)
534 | {
535 | bytes_to_skip -= bytes_actually_skipped;
536 | }
537 | else if (bytes_actually_skipped == 0)
538 | {
539 | if (f.read() == -1)
540 | {
541 | break;
542 | }
543 | else
544 | {
545 | bytes_to_skip--;
546 | }
547 | }
548 | }
549 | }
550 |
551 | private int getShort() throws IOException
552 | {
553 | byte[] bytes = new byte[2];
554 | f.read(bytes);
555 | return Short.toUnsignedInt(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getShort());
556 | }
557 |
558 | private int getInt() throws IOException
559 | {
560 | byte[] bytes = new byte[4];
561 | f.read(bytes);
562 | return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
563 | }
564 |
565 | private long getLong() throws IOException
566 | {
567 | byte[] bytes = new byte[8];
568 | f.read(bytes);
569 | return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getLong();
570 | }
571 |
572 | private String getString(int length) throws IOException
573 | {
574 | byte[] bytes = new byte[length];
575 | f.read(bytes);
576 | try
577 | {
578 | return new String(bytes, "UTF-8").trim();
579 | } catch (UnsupportedEncodingException e)
580 | {
581 | e.printStackTrace();
582 | }
583 | return "UNKNOWN";
584 | }
585 |
586 | }
--------------------------------------------------------------------------------
/src/statistics/Statistics.java:
--------------------------------------------------------------------------------
1 | package statistics;
2 |
3 | import java.awt.Font;
4 | import java.awt.Point;
5 | import java.io.IOException;
6 | import java.util.ArrayList;
7 | import java.util.Arrays;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.ListIterator;
11 | import java.util.Map;
12 |
13 | import org.knowm.xchart.BitmapEncoder;
14 | import org.knowm.xchart.BitmapEncoder.BitmapFormat;
15 | import org.knowm.xchart.XYChart;
16 | import org.knowm.xchart.XYChartBuilder;
17 | import org.knowm.xchart.style.Styler.LegendPosition;
18 |
19 | import boon.AbstractBoon;
20 | import boon.BoonFactory;
21 | import data.AgentItem;
22 | import data.BossData;
23 | import data.CombatData;
24 | import data.SkillData;
25 | import enums.Boon;
26 | import enums.CustomSkill;
27 | import enums.Result;
28 | import enums.StateChange;
29 | import player.BoonLog;
30 | import player.DamageLog;
31 | import player.Player;
32 | import utility.TableBuilder;
33 | import utility.Utility;
34 |
35 | public class Statistics
36 | {
37 | // Fields
38 | public static boolean hiding_players;
39 | BossData b_data;
40 | SkillData s_data;
41 | CombatData c_data;
42 | List p_list;
43 |
44 | // Constructor
45 | public Statistics(Parse parsed)
46 | {
47 | // Data
48 | this.b_data = parsed.getBossData();
49 | this.s_data = parsed.getSkillData();
50 | this.c_data = parsed.getCombatData();
51 |
52 | // Players
53 | p_list = new ArrayList();
54 | List playerAgentList = parsed.getAgentData().getPlayerAgentList();
55 | for (AgentItem playerAgent : playerAgentList)
56 | {
57 | this.p_list.add(new Player(playerAgent));
58 | }
59 |
60 | // Sort
61 | p_list.sort((a, b) -> Integer.parseInt(a.getGroup()) - Integer.parseInt(b.getGroup()));
62 | }
63 |
64 | // Final DPS
65 | public String getFinalDPS()
66 | {
67 | double total_dps = 0.0;
68 | double total_damage = 0.0;
69 | double fight_duration = (b_data.getLastAware() - b_data.getFirstAware()) / 1000.0;
70 |
71 | // Table
72 | TableBuilder table = new TableBuilder();
73 | table.addTitle("Final DPS - " + b_data.getName());
74 |
75 | // Header
76 | table.addRow("Name", "Profession", "DPS", "Damage");
77 |
78 | // Body
79 | for (Player p : p_list)
80 | {
81 | // Damage and DPS
82 | double damage = p.getDamageLogs(b_data, c_data.getCombatList()).stream().mapToDouble(DamageLog::getDamage)
83 | .sum();
84 | double dps = 0.0;
85 | if (fight_duration > 0)
86 | {
87 | dps = damage / fight_duration;
88 | }
89 |
90 | // Totals
91 | total_dps += dps;
92 | total_damage += damage;
93 |
94 | // Add Row
95 | table.addRow(p.getCharacter(), p.getProf(), String.format("%.2f", dps), String.format("%.0f", damage));
96 | }
97 |
98 | // Sort by DPS
99 | table.sortAsDouble(2);
100 |
101 | // Footer
102 | table.addSeparator();
103 | table.addRow("GROUP TOTAL", "-", String.format("%.2f", total_dps), String.format("%.0f", total_damage));
104 | table.addRow("TARGET HEALTH", "-", "-", String.format("%d", b_data.getHealth()));
105 |
106 | return table.toString();
107 | }
108 |
109 | // Phase DPS
110 | public String getPhaseDPS()
111 | {
112 | double total_time = 0.0;
113 | List fight_intervals = getPhaseIntervals();
114 | int n = fight_intervals.size();
115 | String[] phase_names = b_data.getPhaseNames();
116 |
117 | // Table
118 | TableBuilder table = new TableBuilder();
119 | table.addTitle("Phase DPS - " + b_data.getName());
120 |
121 | // Header
122 | String[] header = new String[n + 3];
123 | header[0] = "Name";
124 | header[1] = "Profession";
125 | for (int i = 2; i < n + 2; i++)
126 | {
127 | header[i] = phase_names[i - 2];
128 | }
129 | header[header.length - 1] = "Summary";
130 | table.addRow(header);
131 |
132 | // Body
133 | for (Player p : p_list)
134 | {
135 | total_time = 0.0;
136 | double total_damage = 0.0;
137 | String[] phase_dps = new String[n + 1];
138 |
139 | for (int i = 0; i < n; i++)
140 | {
141 | List damage_logs = p.getDamageLogs(b_data, c_data.getCombatList());
142 | Point interval = fight_intervals.get(i);
143 |
144 | // Damage and DPS
145 | double phase_damage = 0.0;
146 | for (DamageLog log : damage_logs)
147 | {
148 | if ((log.getTime() >= interval.x) && (log.getTime() <= interval.y))
149 | {
150 | phase_damage += log.getDamage();
151 | }
152 | }
153 | total_time += (interval.getY() - interval.getX());
154 | total_damage += phase_damage;
155 | double dps = (phase_damage / (interval.getY() - interval.getX())) * 1000.0;
156 | phase_dps[i] = String.format("%.2f", dps);
157 |
158 | }
159 | // Row
160 | phase_dps[n] = String.format("%.2f", (total_damage / total_time) * 1000.0);
161 | table.addRow(Utility.concatStringArray(new String[] { p.getCharacter(), p.getProf() }, phase_dps));
162 | }
163 |
164 | // Sort
165 | table.sortAsDouble(n + 2);
166 |
167 | // Footer
168 | table.addSeparator();
169 | String[] durations = new String[n + 3];
170 | durations[0] = "PHASE DURATION";
171 | durations[1] = "-";
172 | for (int i = 2; i < n + 2; i++)
173 | {
174 | Point p = fight_intervals.get(i - 2);
175 | double time = (p.getY() - p.getX()) / 1000.0;
176 | durations[i] = String.format("%.3f", time);
177 | }
178 | durations[durations.length - 1] = String.format("%.3f", total_time / 1000.0);
179 | table.addRow(durations);
180 |
181 | String[] intervals = new String[n + 3];
182 | intervals[0] = "PHASE INTERVAL";
183 | intervals[1] = "-";
184 | for (int i = 2; i < n + 2; i++)
185 | {
186 | Point p = fight_intervals.get(i - 2);
187 | intervals[i] = String.format("%.3f", p.getX() / 1000.0) + " - " + String.format("%.3f", p.getY() / 1000.0);
188 | }
189 | intervals[intervals.length - 1] = String.format("%.3f", fight_intervals.get(0).getX() / 1000.0) + " - "
190 | + String.format("%.3f", fight_intervals.get(fight_intervals.size() - 1).getY() / 1000.0);
191 | table.addRow(intervals);
192 | return table.toString();
193 | }
194 |
195 | // Damage Distribution
196 | public String getDamageDistribution()
197 | {
198 | // Heading
199 | StringBuilder output = new StringBuilder();
200 | String title = " Damage Distribution - " + b_data.getName() + ' ';
201 | output.append('\u250C' + Utility.fillWithChar(title.length(), '\u2500') + '\u2510' + System.lineSeparator());
202 | output.append('\u2502' + title + '\u2502' + System.lineSeparator());
203 | output.append('\u2514' + Utility.fillWithChar(title.length(), '\u2500') + '\u2518');
204 |
205 | // Table
206 | TableBuilder table = new TableBuilder();
207 |
208 | // Body
209 | for (Player p : p_list)
210 | {
211 | List damage_logs = p.getDamageLogs(b_data, c_data.getCombatList());
212 |
213 | // Skill Damage Map
214 | Map skill_damage = new HashMap();
215 | for (DamageLog log : damage_logs)
216 | {
217 | if (skill_damage.containsKey(log.getID()))
218 | {
219 | skill_damage.put(log.getID(), skill_damage.get(log.getID()) + log.getDamage());
220 | }
221 | else
222 | {
223 | skill_damage.put(log.getID(), log.getDamage());
224 | }
225 | }
226 |
227 | // Title and Header
228 | table.clear();
229 | table.addTitle(p.getCharacter() + " - " + p.getProf());
230 | table.addRow("Skill", "Damage", "%");
231 |
232 | // Sort
233 | skill_damage = Utility.sortByValue(skill_damage);
234 |
235 | // Calculate distribution
236 | double total_damage = skill_damage.values().stream().reduce(0, Integer::sum);
237 | for (Map.Entry entry : skill_damage.entrySet())
238 | {
239 | String skill_name = s_data.getName(entry.getKey());
240 | double damage = entry.getValue();
241 | table.addRow(skill_name, String.format("%.0f", damage),
242 | String.format("%.2f", (damage / total_damage * 100.0)));
243 | }
244 |
245 | // Add table
246 | output.append(System.lineSeparator());
247 | output.append(table.toString());
248 | }
249 | return output.toString();
250 | }
251 |
252 | // Total Damage Graph
253 | public String getTotalDamageGraph(String base)
254 | {
255 | // Build
256 | XYChartBuilder chartBuilder = new XYChartBuilder().width(1600).height(900);
257 | chartBuilder.title("Total Damage - " + b_data.getName());
258 | chartBuilder.xAxisTitle("Time (seconds)").yAxisTitle("Damage (K)").build();
259 | XYChart chart = chartBuilder.build();
260 |
261 | // Style
262 | chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
263 | chart.getStyler().setMarkerSize(1);
264 | chart.getStyler().setXAxisMin(0.0);
265 | chart.getStyler().setYAxisMin(0.0);
266 | chart.getStyler().setLegendFont(new Font("Dialog", Font.PLAIN, 16));
267 |
268 | // Series
269 | for (Player p : p_list)
270 | {
271 | List damage_logs = p.getDamageLogs(b_data, c_data.getCombatList());
272 | int n = damage_logs.size();
273 | if (n > 0)
274 | {
275 | double[] x = new double[n];
276 | double[] y = new double[n];
277 | double total_damage = 0.0;
278 | for (int i = 0; i < n; i++)
279 | {
280 | total_damage += damage_logs.get(i).getDamage();
281 | x[i] = damage_logs.get(i).getTime() / 1000.0;
282 | y[i] = total_damage / 1000.0;
283 | }
284 | chart.addSeries(p.getCharacter() + " - " + p.getProf(), x, y);
285 | }
286 | }
287 |
288 | // Write
289 | try
290 | {
291 | String file_name = "./graphs/" + base + "_" + b_data.getName() + "_TDG.png";
292 | BitmapEncoder.saveBitmapWithDPI(chart, file_name, BitmapFormat.PNG, 300);
293 | } catch (IOException e)
294 | {
295 | e.printStackTrace();
296 | }
297 | return base + "_" + b_data.getName() + "_TDG.png";
298 | }
299 |
300 | // Combat Statistics
301 | public String getCombatStatistics()
302 | {
303 | // Table
304 | TableBuilder table = new TableBuilder();
305 | table.addTitle("Combat Statistics - " + b_data.getName());
306 |
307 | // Header
308 | table.addRow("Account", "Character", "Profession", "SUBG", "CRIT", "SCHL", "MOVE", "FLNK", "TGHN", "HEAL",
309 | "COND", "SWAP", "DOGE", "RESS", "DOWN", "DIED");
310 |
311 | // Body
312 | for (Player p : p_list)
313 | {
314 | List damage_logs = p.getDamageLogs(b_data, c_data.getCombatList());
315 | int instid = p.getInstid();
316 |
317 | // Rates
318 | double power_loop_count = 0.0;
319 | double critical_rate = 0.0;
320 | double scholar_rate = 0.0;
321 | double moving_rate = 0.0;
322 | double flanking_rate = 0.0;
323 | for (DamageLog log : damage_logs)
324 | {
325 | if (log.isCondi() == 0)
326 | {
327 | critical_rate += (log.getResult().equals(Result.CRIT)) ? 1 : 0;
328 | scholar_rate += log.isNinety();
329 | moving_rate += log.isMoving();
330 | flanking_rate += log.isFlanking();
331 | power_loop_count++;
332 | }
333 | }
334 | power_loop_count = (power_loop_count == 0) ? 1 : power_loop_count;
335 |
336 | // Counts
337 | int swap = c_data.getStates(instid, StateChange.WEAPON_SWAP).size();
338 | int down = c_data.getStates(instid, StateChange.CHANGE_DOWN).size();
339 | int dodge = c_data.getSkillCount(instid, CustomSkill.DODGE.getID());
340 | int ress = c_data.getSkillCount(instid, CustomSkill.RESURRECT.getID());
341 |
342 | // R.I.P
343 | List dead = c_data.getStates(instid, StateChange.CHANGE_DEAD);
344 | double died = 0.0;
345 | if (!dead.isEmpty())
346 | {
347 | died = dead.get(0).getX() - b_data.getFirstAware();
348 | }
349 |
350 | // Add row
351 | table.addRow(new String[] { p.getAccount(), p.getCharacter(), p.getProf(), p.getGroup(),
352 | String.format("%.2f", critical_rate / power_loop_count),
353 | String.format("%.2f", scholar_rate / power_loop_count),
354 | String.format("%.2f", moving_rate / power_loop_count),
355 | String.format("%.2f", flanking_rate / power_loop_count), String.valueOf(p.getToughness()),
356 | String.valueOf(p.getHealing()), String.valueOf(p.getCondition()), String.valueOf(swap),
357 | String.valueOf(dodge), String.valueOf(ress), String.valueOf(down),
358 | String.format("%.2f", died / 1000.0) });
359 | }
360 | return table.toString();
361 | }
362 |
363 | // Final Boons
364 | public String getFinalBoons()
365 | {
366 | // Table
367 | TableBuilder table = new TableBuilder();
368 | table.addTitle("Final Boon Rates - " + b_data.getName());
369 |
370 | // Header
371 | String[] boon_array = Boon.getArray();
372 | table.addRow(Utility.concatStringArray(new String[] { "Name", "Profession" }, boon_array));
373 |
374 | // Body
375 | List boon_list = Boon.getList();
376 | int n = boon_list.size();
377 | BoonFactory boonFactory = new BoonFactory();
378 |
379 | for (Player p : p_list)
380 | {
381 | Map> boon_logs = p.getBoonMap(b_data, s_data, c_data.getCombatList());
382 | String[] rates = new String[n];
383 | for (int i = 0; i < n; i++)
384 | {
385 | Boon boon = Boon.getEnum(boon_list.get(i));
386 | AbstractBoon boon_object = boonFactory.makeBoon(boon);
387 | List logs = boon_logs.get(boon.getName());
388 | String rate = "0.00";
389 | if (!logs.isEmpty())
390 | {
391 | if (boon.getType().equals("duration"))
392 | {
393 | rate = getBoonDuration(getBoonIntervalsList(boon_object, logs));
394 | }
395 | else if (boon.getType().equals("intensity"))
396 | {
397 | rate = getAverageStacks(getBoonStacksList(boon_object, logs));
398 | }
399 | }
400 | rates[i] = rate;
401 | }
402 | table.addRow(Utility.concatStringArray(new String[] { p.getCharacter(), p.getProf() }, rates));
403 | }
404 | return table.toString();
405 | }
406 |
407 | // Phase Boons
408 | public String getPhaseBoons()
409 | {
410 | BoonFactory boonFactory = new BoonFactory();
411 | List all_rates = new ArrayList();
412 | List boon_list = Boon.getList();
413 | List fight_intervals = getPhaseIntervals();
414 | int n = fight_intervals.size();
415 | int m = boon_list.size();
416 |
417 | for (Player p : p_list)
418 | {
419 | Map> boon_logs = p.getBoonMap(b_data, s_data, c_data.getCombatList());
420 | String[][] rates = new String[m][];
421 | for (int j = 0; j < m; j++)
422 | {
423 | Boon boon = Boon.getEnum(boon_list.get(j));
424 | String[] rate = new String[fight_intervals.size()];
425 | Arrays.fill(rate, "0.00");
426 | List logs = boon_logs.get(boon.getName());
427 | if (!logs.isEmpty())
428 | {
429 | AbstractBoon boon_object = boonFactory.makeBoon(boon);
430 | if (boon.getType().equals("duration"))
431 | {
432 | List boon_intervals = getBoonIntervalsList(boon_object, logs);
433 | rate = getBoonDuration(boon_intervals, fight_intervals);
434 | }
435 | else if (boon.getType().equals("intensity"))
436 | {
437 | List boon_stacks = getBoonStacksList(boon_object, logs);
438 | rate = getAverageStacks(boon_stacks, fight_intervals);
439 | }
440 | }
441 | rates[j] = rate;
442 | }
443 | all_rates.add(rates);
444 | }
445 |
446 | // Heading
447 | StringBuilder output = new StringBuilder();
448 | String title = " Phase Boon Rates - " + b_data.getName() + ' ';
449 | output.append('\u250C' + Utility.fillWithChar(title.length(), '\u2500') + '\u2510' + System.lineSeparator());
450 | output.append('\u2502' + title + '\u2502' + System.lineSeparator());
451 | output.append('\u2514' + Utility.fillWithChar(title.length(), '\u2500') + '\u2518');
452 |
453 | // Table
454 | TableBuilder table = new TableBuilder();
455 |
456 | // Body
457 | String[] boon_array = Boon.getArray();
458 | String[] phase_names = b_data.getPhaseNames();
459 | for (int i = 0; i < n; i++)
460 | {
461 | table.clear();
462 | table.addTitle(phase_names[i]);
463 | table.addRow(Utility.concatStringArray(new String[] { "Name", "Profession" }, boon_array));
464 | int l = p_list.size();
465 | for (int j = 0; j < l; j++)
466 | {
467 | Player p = p_list.get(j);
468 | String[][] player_rates = all_rates.get(j);
469 | String[] row_rates = new String[m];
470 | for (int k = 0; k < m; k++)
471 | {
472 | row_rates[k] = player_rates[k][i];
473 | }
474 | table.addRow(Utility.concatStringArray(new String[] { p.getCharacter(), p.getProf() }, row_rates));
475 | }
476 | output.append(System.lineSeparator() + table.toString());
477 | }
478 | return output.toString();
479 | }
480 |
481 | // Private Methods
482 | private List getPhaseIntervals()
483 | {
484 | List fight_intervals = new ArrayList();
485 | int log_start = c_data.getCombatList().get(0).getTime();
486 | int time_start = b_data.getFirstAware() - log_start;
487 | int time_end = b_data.getLastAware() - log_start;
488 |
489 | // Thresholds
490 | int time_threshold = 0;
491 | int[] health_thresholds = null;
492 | if (b_data.getName().equals("Vale Guardian"))
493 | {
494 | time_threshold = 20000;
495 | health_thresholds = new int[] { 6600, 3300 };
496 | }
497 | else if (b_data.getName().equals("Gorseval the Multifarious"))
498 | {
499 | time_threshold = 20000;
500 | health_thresholds = new int[] { 6600, 3300 };
501 | }
502 | else if (b_data.getName().equals("Sabetha the Saboteur"))
503 | {
504 | time_threshold = 20000;
505 | health_thresholds = new int[] { 7500, 5000, 2500 };
506 | }
507 | else if (b_data.getName().equals("Slothasor"))
508 | {
509 | health_thresholds = new int[] { 8000, 6000, 4000, 2000, 1000 };
510 | }
511 | else if (b_data.getName().equals("Matthias Gabrel"))
512 | {
513 | health_thresholds = new int[] { 8000, 6000, 4000 };
514 | }
515 | else if (b_data.getName().equals("Keep Construct"))
516 | {
517 | time_threshold = 20000;
518 | health_thresholds = new int[] { 6600, 3300 };
519 | }
520 | else if (b_data.getName().equals("Xera"))
521 | {
522 | time_threshold = 20000;
523 | health_thresholds = new int[] { 5000 };
524 | }
525 | else if (b_data.getName().equals("Cairn the Indomitable"))
526 | {
527 | health_thresholds = new int[] { 7500, 5000, 2500 };
528 | }
529 | else if (b_data.getName().equals("Mursaat Overseer"))
530 | {
531 | health_thresholds = new int[] { 7500, 5000, 2500 };
532 | }
533 | else if (b_data.getName().equals("Samarog"))
534 | {
535 | time_threshold = 20000;
536 | health_thresholds = new int[] { 6600, 3300 };
537 | }
538 | else if (b_data.getName().equals("Deimos"))
539 | {
540 | health_thresholds = new int[] { 7500, 5000, 2500 };
541 | }
542 | else
543 | {
544 | fight_intervals.add(new Point(time_start, time_end));
545 | return fight_intervals;
546 | }
547 |
548 | // Generate intervals with health updates
549 | ListIterator iter = c_data.getStates(b_data.getInstid(), StateChange.HEALTH_UPDATE).listIterator();
550 | Point previous_update = null;
551 | if (iter.hasNext())
552 | {
553 | previous_update = iter.next();
554 | }
555 | if (previous_update != null)
556 | {
557 | main: for (int threshold : health_thresholds)
558 | {
559 | while (iter.hasNext())
560 | {
561 | Point current_update = iter.next();
562 | if ((current_update.y <= threshold) && (time_threshold == 0))
563 | {
564 | fight_intervals.add(new Point(time_start, previous_update.x - log_start));
565 | time_start = previous_update.x - log_start;
566 | previous_update = current_update;
567 | continue main;
568 | }
569 | else if ((current_update.y <= threshold)
570 | && ((current_update.x - previous_update.x) > time_threshold))
571 | {
572 | fight_intervals.add(new Point(time_start, previous_update.x - log_start));
573 | time_start = current_update.x - log_start;
574 | previous_update = current_update;
575 | continue main;
576 |
577 | }
578 | previous_update = current_update;
579 | }
580 | }
581 | }
582 | fight_intervals.add(new Point(time_start, time_end));
583 | return fight_intervals;
584 | }
585 |
586 | private List getBoonIntervalsList(AbstractBoon boon, List boon_logs)
587 | {
588 | // Initialise variables
589 | int t_prev = 0;
590 | int t_curr = 0;
591 | List boon_intervals = new ArrayList();
592 |
593 | // Loop: update then add durations
594 | for (BoonLog log : boon_logs)
595 | {
596 | t_curr = log.getTime();
597 | boon.update(t_curr - t_prev);
598 | boon.add(log.getValue());
599 | boon_intervals.add(new Point(t_curr, t_curr + boon.getStackValue()));
600 | t_prev = t_curr;
601 | }
602 |
603 | // Merge intervals
604 | boon_intervals = Utility.mergeIntervals(boon_intervals);
605 |
606 | // Trim duration overflow
607 | int fight_duration = b_data.getLastAware() - b_data.getFirstAware();
608 | int last = boon_intervals.size() - 1;
609 | if (boon_intervals.get(last).getY() > fight_duration)
610 | {
611 | boon_intervals.get(last).y = fight_duration;
612 | }
613 |
614 | return boon_intervals;
615 | }
616 |
617 | private String getBoonDuration(List boon_intervals)
618 | {
619 | // Calculate average duration
620 | double average_duration = 0.0;
621 | for (Point p : boon_intervals)
622 | {
623 | average_duration = average_duration + (p.getY() - p.getX());
624 | }
625 | return String.format("%.2f", (average_duration / (b_data.getLastAware() - b_data.getFirstAware())));
626 | }
627 |
628 | private String[] getBoonDuration(List boon_intervals, List fight_intervals)
629 | {
630 | // Phase durations
631 | String[] phase_durations = new String[fight_intervals.size()];
632 |
633 | // Loop: add intervals in between, merge, calculate duration
634 | for (int i = 0; i < fight_intervals.size(); i++)
635 | {
636 | Point p = fight_intervals.get(i);
637 | List boons_intervals_during_phase = new ArrayList();
638 | for (Point b : boon_intervals)
639 | {
640 | if (b.x < p.y && p.x < b.y)
641 | {
642 | if (p.x <= b.x && b.y <= p.y)
643 | {
644 | boons_intervals_during_phase.add(b);
645 | }
646 | else if (b.x < p.x && p.y < b.y)
647 | {
648 | boons_intervals_during_phase.add(p);
649 | }
650 | else if (b.x < p.x && b.y <= p.y)
651 | {
652 | boons_intervals_during_phase.add(new Point(p.x, b.y));
653 | }
654 | else if (p.x <= b.x && p.y < b.y)
655 | {
656 | boons_intervals_during_phase.add(new Point(b.x, p.y));
657 | }
658 | }
659 | }
660 | double duration = 0.0;
661 | for (Point b : boons_intervals_during_phase)
662 | {
663 | duration = duration + (b.getY() - b.getX());
664 | }
665 | phase_durations[i] = String.format("%.2f", (duration / (p.getY() - p.getX())));
666 | }
667 | return phase_durations;
668 | }
669 |
670 | private List getBoonStacksList(AbstractBoon boon, List boon_logs)
671 | {
672 | // Initialise variables
673 | int t_prev = 0;
674 | int t_curr = 0;
675 | List boon_stacks = new ArrayList();
676 | boon_stacks.add(0);
677 |
678 | // Loop: fill, update, and add to stacks
679 | for (BoonLog log : boon_logs)
680 | {
681 | t_curr = log.getTime();
682 | boon.addStacksBetween(boon_stacks, t_curr - t_prev);
683 | boon.update(t_curr - t_prev);
684 | boon.add(log.getValue());
685 | if (t_curr != t_prev)
686 | {
687 | boon_stacks.add(boon.getStackValue());
688 | }
689 | else
690 | {
691 | boon_stacks.set(boon_stacks.size() - 1, boon.getStackValue());
692 | }
693 | t_prev = t_curr;
694 | }
695 |
696 | // Fill in remaining stacks
697 | boon.addStacksBetween(boon_stacks, b_data.getLastAware() - b_data.getFirstAware() - t_prev);
698 | boon.update(1);
699 | boon_stacks.add(boon.getStackValue());
700 | return boon_stacks;
701 | }
702 |
703 | private String getAverageStacks(List boon_stacks)
704 | {
705 | // Calculate average stacks
706 | double average_stacks = boon_stacks.stream().mapToInt(Integer::intValue).sum();
707 | double average = average_stacks / boon_stacks.size();
708 | if (average > 10.0)
709 | {
710 | return String.format("%.1f", average);
711 | }
712 | else
713 | {
714 | return String.format("%.2f", average);
715 | }
716 | }
717 |
718 | private String[] getAverageStacks(List boon_stacks, List fight_intervals)
719 | {
720 | // Phase stacks
721 | String[] phase_stacks = new String[fight_intervals.size()];
722 |
723 | // Loop: get sublist and calculate average stacks
724 | for (int i = 0; i < fight_intervals.size(); i++)
725 | {
726 | Point p = fight_intervals.get(i);
727 | List phase_boon_stacks = new ArrayList(boon_stacks.subList(p.x, p.y));
728 | double average_stacks = phase_boon_stacks.stream().mapToInt(Integer::intValue).sum();
729 | double average = average_stacks / phase_boon_stacks.size();
730 | if (average > 10.0)
731 | {
732 | phase_stacks[i] = String.format("%.1f", average);
733 | }
734 | else
735 | {
736 | phase_stacks[i] = String.format("%.2f", average);
737 | }
738 | }
739 | return phase_stacks;
740 | }
741 | }
742 |
--------------------------------------------------------------------------------
/src/utility/TableBuilder.java:
--------------------------------------------------------------------------------
1 | package utility;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Collections;
6 | import java.util.Comparator;
7 | import java.util.List;
8 | import java.util.ListIterator;
9 |
10 | public class TableBuilder
11 | {
12 |
13 | // Fields
14 | private String title = "";
15 | private List rows = new ArrayList();
16 | private String nl = System.lineSeparator();
17 |
18 | // Public Methods
19 | public void addTitle(String title)
20 | {
21 | this.title = ' ' + title + ' ';
22 | }
23 |
24 | public void addRow(String... cols)
25 | {
26 | rows.add(cols);
27 | String[] row = rows.get(rows.size() - 1);
28 | for (int i = 0; i < row.length; i++)
29 | {
30 | row[i] = ' ' + row[i] + ' ';
31 | }
32 | }
33 |
34 | public void addSeparator()
35 | {
36 | if (!rows.isEmpty())
37 | {
38 | String[] separator = new String[rows.get(0).length];
39 | Arrays.fill(separator, "");
40 | rows.add(separator);
41 | }
42 | }
43 |
44 | public void sortAsDouble(int col)
45 | {
46 | // Get body
47 | int separator = 0;
48 | for (String[] row : rows)
49 | {
50 | if (row[0].equals(""))
51 | {
52 | break;
53 | }
54 | separator++;
55 | }
56 | List body = rows.subList(1, separator);
57 |
58 | // Sort by column
59 | Collections.sort(body, new Comparator()
60 | {
61 | @Override
62 | public int compare(String[] x, String[] y)
63 | {
64 | double x_dbl = Double.parseDouble(x[col].replaceAll("\\s+", ""));
65 | double y_dbl = Double.parseDouble(y[col].replaceAll("\\s+", ""));
66 | if (x_dbl > y_dbl)
67 | {
68 | return -1;
69 | }
70 | else if (x_dbl == y_dbl)
71 | {
72 | return 0;
73 | }
74 | else
75 | {
76 | return 1;
77 | }
78 | }
79 | });
80 | }
81 |
82 | public void clear()
83 | {
84 | title = "";
85 | rows = new ArrayList();
86 | }
87 |
88 | @Override
89 | public String toString()
90 | {
91 | // Initialize
92 | removeEmptyColumns();
93 | StringBuilder output = new StringBuilder();
94 | int[] colWidths = getWidths();
95 | int numCols = rows.get(0).length;
96 |
97 | // Title
98 | if (!title.equals(""))
99 | {
100 | output.append('\u250C' + Utility.fillWithChar(title.length(), '\u2500') + '\u2510' + nl);
101 | output.append('\u2502' + title + '\u2502' + nl);
102 | output.append('\u2514' + Utility.fillWithChar(title.length(), '\u2500') + '\u2518' + nl);
103 | }
104 |
105 | // Empty
106 | if (rows.size() <= 1)
107 | {
108 | return output.toString();
109 | }
110 |
111 | // Header
112 | output.append('\u250C');
113 | for (int colNum = 0; colNum < rows.get(0).length; colNum++)
114 | {
115 | output.append(Utility.fillWithChar(colWidths[colNum], '\u2500'));
116 | if (colNum != numCols - 1)
117 | {
118 | output.append('\u252C');
119 | }
120 | else
121 | {
122 | output.append('\u2510' + nl + '\u2502');
123 | }
124 | }
125 | for (int colNum = 0; colNum < numCols; colNum++)
126 | {
127 | output.append(Utility.centerString(rows.get(0)[colNum], colWidths[colNum]) + '\u2502');
128 | }
129 | output.append(nl + '\u251C');
130 | for (int colNum = 0; colNum < rows.get(0).length; colNum++)
131 | {
132 | output.append(Utility.fillWithChar(colWidths[colNum], '\u2550'));
133 | if (colNum != numCols - 1)
134 | {
135 | output.append('\u253C');
136 | }
137 | else
138 | {
139 | output.append('\u2524' + nl);
140 | }
141 | }
142 |
143 | // Body
144 | boolean footer = false;
145 | for (ListIterator iter = rows.listIterator(1); iter.hasNext();)
146 | {
147 | String[] row = iter.next();
148 | String text;
149 |
150 | for (int colNum = 0; colNum < row.length; colNum++)
151 | {
152 | if (row[0].equals(""))
153 | {
154 |
155 | footer = true;
156 | if (colNum == 0)
157 | {
158 | output.append('\u251C');
159 | }
160 | else
161 | {
162 | output.append('\u253C');
163 | }
164 | output.append(Utility.fillWithChar(colWidths[colNum], '\u2500'));
165 | }
166 | else
167 | {
168 | text = row[colNum];
169 | if (!footer)
170 | {
171 | if (Utility.isNumeric(text))
172 | {
173 | output.append('\u2502' + Utility.rightAlignString(text, colWidths[colNum]));
174 | }
175 | else
176 | {
177 | output.append('\u2502' + Utility.leftAlignString(text, colWidths[colNum]));
178 | }
179 | }
180 | else
181 | {
182 | output.append('\u2502' + Utility.centerString(text, colWidths[colNum]));
183 | }
184 | }
185 | }
186 | if (row[0].equals(""))
187 | {
188 | output.append('\u2524' + nl);
189 | }
190 | else
191 | {
192 | output.append('\u2502' + nl);
193 | }
194 | }
195 | output.append('\u2514');
196 | for (int colNum = 0; colNum < rows.get(0).length; colNum++)
197 | {
198 | output.append(Utility.fillWithChar(colWidths[colNum], '\u2500'));
199 | if (colNum != numCols - 1)
200 | {
201 | output.append('\u2534');
202 | }
203 | else
204 | {
205 | output.append('\u2518');
206 | }
207 | }
208 |
209 | return output.toString();
210 | }
211 |
212 | // Private Methods
213 | private int[] getWidths()
214 | {
215 | int cols = 0;
216 | for (String[] row : rows)
217 | cols = Math.max(cols, row.length);
218 | int[] widths = new int[cols];
219 | for (String[] row : rows)
220 | {
221 | for (int i = 0; i < row.length; i++)
222 | {
223 | widths[i] = Math.max(widths[i], row[i].length());
224 | }
225 | }
226 | return widths;
227 | }
228 |
229 | private void removeEmptyColumns()
230 | {
231 |
232 | // Columns that contain all "0.00" will have existance[i] = false
233 | int cols = rows.get(0).length;
234 | boolean[] existance = new boolean[cols];
235 | for (ListIterator iter = rows.listIterator(1); iter.hasNext();)
236 | {
237 | String[] row = iter.next();
238 | for (int i = 0; i < cols; i++)
239 | {
240 | if (!existance[i] && !row[i].equals(" 0.00 "))
241 | {
242 | existance[i] = true;
243 | }
244 | }
245 | }
246 |
247 | // Check if there are any false values
248 | int exist_count = 0;
249 | for (boolean exists : existance)
250 | {
251 | if (exists)
252 | {
253 | exist_count++;
254 | }
255 | }
256 | if (exist_count == cols)
257 | {
258 | return;
259 | }
260 |
261 | // Create new table with no empty columns
262 | List new_rows = new ArrayList();
263 | for (String[] row : rows)
264 | {
265 | int i = 0;
266 | int j = 0;
267 | String[] new_row = new String[exist_count];
268 | for (boolean exists : existance)
269 | {
270 | if (exists)
271 | {
272 | new_row[i] = row[j];
273 | i++;
274 | }
275 | j++;
276 | }
277 | new_rows.add(new_row);
278 | }
279 | rows = new_rows;
280 |
281 | }
282 |
283 | }
--------------------------------------------------------------------------------
/src/utility/Utility.java:
--------------------------------------------------------------------------------
1 | package utility;
2 |
3 | import java.awt.Point;
4 | import java.io.BufferedReader;
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.io.PrintWriter;
8 | import java.io.StringReader;
9 | import java.nio.file.Path;
10 | import java.util.ArrayList;
11 | import java.util.Arrays;
12 | import java.util.Collections;
13 | import java.util.LinkedHashMap;
14 | import java.util.List;
15 | import java.util.Map;
16 | import java.util.stream.Collectors;
17 |
18 | public final class Utility
19 | {
20 |
21 | public static boolean toBool(int i)
22 | {
23 | return (i != 0);
24 | }
25 |
26 | public static String boxText(String text)
27 | {
28 | StringBuilder boxedText = new StringBuilder();
29 | boxedText.append(
30 | "\u250C" + Utility.fillWithChar(text.length() + 2, '\u2500') + "\u2510" + System.lineSeparator());
31 | boxedText.append("\u2502 " + text + " \u2502 " + System.lineSeparator());
32 | boxedText.append("\u2514" + Utility.fillWithChar(text.length() + 2, '\u2500') + "\u2518");
33 | return boxedText.toString();
34 | }
35 |
36 | public static void writeToFile(String s, File f) throws IOException
37 | {
38 | try (BufferedReader getter = new BufferedReader(new StringReader(s));
39 | PrintWriter writer = new PrintWriter(f, "UTF-8");)
40 | {
41 | getter.lines().forEach(line -> writer.println(line));
42 | writer.close();
43 | }
44 | }
45 |
46 | public static void recursiveFileSearch(File dir, List files)
47 | {
48 | File[] file_array = dir.listFiles();
49 | for (File f : file_array)
50 | {
51 | if (f.isFile() && f.toString().endsWith(".evtc") || f.toString().endsWith(".zip"))
52 | {
53 | files.add(f.toPath());
54 | }
55 | else if (f.isDirectory())
56 | {
57 | recursiveFileSearch(f, files);
58 | }
59 | }
60 | }
61 |
62 | public static > Map sortByValue(Map map)
63 | {
64 | return map.entrySet().stream().sorted(Map.Entry.comparingByValue(Collections.reverseOrder()))
65 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
66 | }
67 |
68 | public static String centerString(String text, int len)
69 | {
70 | String output = String.format("%" + len + "s%s%" + len + "s", "", text, "");
71 | float start = (output.length() / 2) - (len / 2);
72 | return output.substring((int) start, (int) (start + len));
73 | }
74 |
75 | public static boolean isNumeric(String str)
76 | {
77 | try
78 | {
79 | Double.parseDouble(str);
80 | } catch (NumberFormatException nfe)
81 | {
82 | return false;
83 | }
84 | return true;
85 | }
86 |
87 | public static String rightAlignString(String text, int len)
88 | {
89 | while (text.length() < len)
90 | {
91 | text = " " + text;
92 |
93 | }
94 | return text;
95 | }
96 |
97 | public static String leftAlignString(String text, int len)
98 | {
99 | while (text.length() < len)
100 | {
101 | text += " ";
102 |
103 | }
104 | return text;
105 | }
106 |
107 | public static String fillWithChar(int len, char c)
108 | {
109 | if (len > 0)
110 | {
111 | char[] array = new char[len];
112 | Arrays.fill(array, c);
113 | return new String(array);
114 | }
115 | return "";
116 | }
117 |
118 | public static List mergeIntervals(List intervals)
119 | {
120 |
121 | if (intervals.size() == 1)
122 | {
123 | return intervals;
124 | }
125 |
126 | List merged = new ArrayList();
127 | int x = intervals.get(0).x;
128 | int y = intervals.get(0).y;
129 |
130 | for (int i = 1; i < intervals.size(); i++)
131 | {
132 | Point current = intervals.get(i);
133 | if (current.x <= y)
134 | {
135 | y = Math.max(current.y, y);
136 | }
137 | else
138 | {
139 | merged.add(new Point(x, y));
140 | x = current.x;
141 | y = current.y;
142 | }
143 | }
144 |
145 | merged.add(new Point(x, y));
146 |
147 | return merged;
148 | }
149 |
150 | public static String[] concatStringArray(String[] a, String[] b)
151 | {
152 | int i = a.length;
153 | int j = b.length;
154 | String[] output = new String[i + j];
155 | System.arraycopy(a, 0, output, 0, i);
156 | System.arraycopy(b, 0, output, i, j);
157 | return output;
158 | }
159 |
160 | }
--------------------------------------------------------------------------------