├── .gitignore ├── Overlays └── Fancy_Detail_Overlay.zip ├── PercentilePlugin ├── PercentilePlugin.Parser │ ├── App.config │ ├── PercentilePlugin.Parser.csproj │ ├── PercentilePlugin.Parser.csproj.DotSettings │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config ├── PercentilePlugin.Shared │ ├── ClassJob.cs │ ├── DateTimeExtension.cs │ ├── Encounter.cs │ ├── Instance.cs │ ├── PercentileData.cs │ ├── PercentilePlugin.Shared.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config ├── PercentilePlugin.sln └── PercentilePlugin │ ├── Logger.cs │ ├── Options.cs │ ├── PercentilePlugin.cs │ ├── PercentilePlugin.csproj │ ├── PercentileUi.Designer.cs │ ├── PercentileUi.cs │ ├── PercentileUi.resx │ ├── Properties │ └── AssemblyInfo.cs │ ├── VersionChecker.cs │ ├── app.config │ └── packages.config ├── README.md └── parsedata.bin /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /Overlays/Fancy_Detail_Overlay.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liquidize/FFXIV_PercentilePlugin/611d147b8c828d3f45d1d84a9c88c322e51563c8/Overlays/Fancy_Detail_Overlay.zip -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Parser/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Parser/PercentilePlugin.Parser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7D0A1B52-778B-4897-91BB-FB4967439A66} 8 | Exe 9 | PercentilePlugin 10 | PercentilePlugin.Parser 11 | v4.6.1 12 | 512 13 | 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 7.1 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 7.1 35 | 36 | 37 | 38 | ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll 39 | 40 | 41 | ..\packages\Newtonsoft.Json.Bson.1.0.1\lib\net45\Newtonsoft.Json.Bson.dll 42 | 43 | 44 | ..\packages\NLog.4.5.10\lib\net45\NLog.dll 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {76c3faf6-98dd-48c1-808b-30a9f879e5a7} 71 | PercentilePlugin.Shared 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Parser/PercentilePlugin.Parser.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | CSharp71 -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Parser/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Bson; 10 | using Newtonsoft.Json.Linq; 11 | using NLog; 12 | using NLog.Config; 13 | using NLog.Targets; 14 | using PercentilePlugin.Shared; 15 | 16 | namespace PercentilePlugin 17 | { 18 | internal class Program 19 | { 20 | public static Logger Logger; 21 | 22 | private static List jobs; 23 | private static List instances; 24 | private static PercentileData percentileData; 25 | 26 | private static readonly string APIKey = ""; 27 | 28 | public static async Task Main(string[] args) 29 | { 30 | var config = new LoggingConfiguration(); 31 | var consoleTarget = new ColoredConsoleTarget("target1") 32 | { 33 | Layout = @"${date:format=HH\:mm\:ss} ${level} ${message} ${exception}" 34 | }; 35 | config.AddTarget(consoleTarget); 36 | 37 | var fileTarget = new FileTarget("target2") 38 | { 39 | FileName = "${basedir}/file.txt", 40 | Layout = "${longdate} ${level} ${message} ${exception}" 41 | }; 42 | config.AddTarget(fileTarget); 43 | config.AddRuleForOneLevel(LogLevel.Error, fileTarget); // only errors to file 44 | config.AddRuleForAllLevels(consoleTarget); // all to console 45 | 46 | LogManager.Configuration = config; 47 | 48 | Logger = LogManager.GetLogger("Main"); 49 | 50 | // Backup data before we begin. 51 | if (File.Exists("parsedata.bin")) 52 | { 53 | if (File.Exists("parsedata.bak")) File.Delete("parsedata.bak"); 54 | Logger.Log(LogLevel.Info, "Backing Up Parse Data."); 55 | File.Copy("parsedata.bin", "parsedata.bak"); 56 | } 57 | 58 | Logger.Log(LogLevel.Info, "Loading Parse Data."); 59 | percentileData = PercentileData.Load("parsedata.bin"); 60 | Logger.Log(LogLevel.Info, "Parse Data Loaded, Last Update: " + percentileData.LastUpdated); 61 | 62 | 63 | Logger.Log(LogLevel.Info, "Obtaining Job/Class Data."); 64 | await BuildClasses(APIKey); 65 | Logger.Log(LogLevel.Info, "Job/Class Data Obtained."); 66 | Logger.Log(LogLevel.Info, "Obtaining Zone/Instance Data."); 67 | await BuildInstances(APIKey); 68 | Logger.Log(LogLevel.Info, "Zone/Instance Data Obtained."); 69 | Logger.Log(LogLevel.Info, "Obtaining latest Parse Data."); 70 | await BuildPercentiles(); 71 | Logger.Log(LogLevel.Info, "Latest Parse Data Obtained."); 72 | Logger.Log(LogLevel.Info, "Cleaning Up New Data."); 73 | 74 | // Remove Duplicated Entries. 75 | var distinctDictionary = 76 | new Dictionary>>(percentileData.Rankings); 77 | 78 | 79 | Logger.Log(LogLevel.Info, "Removing Unused Fights."); 80 | 81 | // Remove unused fights 82 | foreach (var encounter in distinctDictionary.Keys) 83 | { 84 | var used = false; 85 | foreach (var instance in instances) 86 | if (instance.Encounters.FirstOrDefault(e => e.Value.Name.ToLower() == encounter.ToLower()).Value != 87 | null) 88 | { 89 | Logger.Log(LogLevel.Debug, encounter + " is used."); 90 | used = true; 91 | } 92 | 93 | if (!used) Logger.Log(LogLevel.Warn, "Encounter: " + encounter + " Is no longer needed."); 94 | } 95 | 96 | Logger.Log(LogLevel.Info, "Unused Fights Removed."); 97 | 98 | percentileData.Rankings = distinctDictionary; 99 | 100 | Logger.Log(LogLevel.Info, "Saving Parse Data."); 101 | 102 | // Save 103 | var file = new FileStream("parsedata.bin", FileMode.OpenOrCreate); 104 | using (var writer = new BsonWriter(file)) 105 | { 106 | var serializer = new JsonSerializer(); 107 | serializer.Serialize(writer, percentileData); 108 | } 109 | file.Close(); 110 | File.WriteAllText("parsejson.json", JsonConvert.SerializeObject(percentileData, Formatting.Indented)); 111 | Logger.Log(LogLevel.Info, "Parse Data Saved."); 112 | Console.ReadKey(); 113 | } 114 | 115 | public static double GetRealPercentile(double DPS, double[] sequence) 116 | { 117 | if (sequence.Length == 0) return 100; 118 | Array.Sort(sequence); 119 | var l = 0; 120 | var r = sequence.Length - 1; 121 | var index = sequence.Length / 2; 122 | 123 | while (l <= r) 124 | { 125 | index = l + (r - l) / 2; 126 | 127 | if (sequence[index] < DPS) 128 | l = index + 1; 129 | else 130 | r = index - 1; 131 | } 132 | 133 | return (100 * index + sequence.Length) / sequence.Length; 134 | } 135 | 136 | public static async Task BuildClasses(string apiKey) 137 | { 138 | if (jobs != null) 139 | jobs.Clear(); // Safety net that should never be needed. 140 | else 141 | jobs = new List(); 142 | 143 | try 144 | { 145 | var request = WebRequest.Create("https://www.fflogs.com:443/v1/classes?api_key=" + apiKey); 146 | var response = await request.GetResponseAsync(); 147 | if (response.GetResponseStream() != null) 148 | using (var reader = 149 | new StreamReader(response.GetResponseStream() ?? throw new InvalidOperationException())) 150 | { 151 | var json = reader.ReadToEnd(); 152 | var array = JArray.Parse(json); 153 | if (array.HasValues) 154 | { 155 | var obj = array.First; 156 | if (obj.HasValues) 157 | { 158 | var specs = obj["specs"].ToObject(); 159 | if (specs.HasValues) 160 | for (var i = 0; i < specs.Count; i++) 161 | { 162 | var job = specs[i]; 163 | var cjob = new ClassJob(); 164 | cjob.Key = job["id"].ToObject(); 165 | cjob.Name = Convert.ToString(job["name"]); 166 | cjob.Abbrveiation = ClassJob.NameToAbbr(cjob.Name).ToUpper(); 167 | jobs.Add(cjob); 168 | Logger.Log(LogLevel.Info, string.Format("Parsed Job: {0}.", cjob.Name)); 169 | } 170 | } 171 | } 172 | } 173 | 174 | return await Task.FromResult(true); 175 | } 176 | catch (InvalidOperationException invalidOperation) 177 | { 178 | Logger.Log(LogLevel.Fatal, "Invalid Operation Occurred, Response Stream for Jobs is null."); 179 | Logger.Log(LogLevel.Fatal, invalidOperation.ToString()); 180 | return await Task.FromResult(false); 181 | } 182 | catch (Exception ex) 183 | { 184 | Logger.Log(LogLevel.Fatal, ex.ToString()); 185 | return await Task.FromResult(false); 186 | } 187 | } 188 | 189 | public static async Task BuildInstances(string apiKey) 190 | { 191 | if (instances != null) 192 | instances.Clear(); // safety net that should never be needed 193 | else 194 | instances = new List(); 195 | 196 | try 197 | { 198 | var request = WebRequest.Create("https://www.fflogs.com:443/v1/zones?api_key=" + apiKey); 199 | var response = await request.GetResponseAsync(); 200 | if (response.GetResponseStream() != null) 201 | using (var reader = 202 | new StreamReader(response.GetResponseStream() ?? throw new InvalidOperationException())) 203 | { 204 | var json = reader.ReadToEnd(); 205 | var array = JArray.Parse(json); 206 | if (array.HasValues) 207 | foreach (var category in array) 208 | if (category["frozen"].ToObject() == false && 209 | category["id"].ToObject() != 2 && category["id"].ToObject() != 14) 210 | foreach (var encounter in category["encounters"]) 211 | { 212 | var encObj = new Encounter(); 213 | encObj.Name = 214 | category["id"].ToObject() == 21 || category["id"].ToObject() == 25 215 | ? encounter["name"].ToObject() + " (Savage)" 216 | : encounter["name"].ToObject(); 217 | encObj.Key = encounter["id"].ToObject(); 218 | encObj.Category = category["id"].ToObject(); 219 | 220 | var instance = instances.FirstOrDefault(i => 221 | i.MapName == Instance.InstanceFromBoss(encObj.Name)); 222 | 223 | if (instance == null) 224 | { 225 | instance = new Instance(); 226 | instance.MapName = Instance.InstanceFromBoss(encObj.Name); 227 | instance.Encounters = new Dictionary(); 228 | instances.Add(instance); 229 | } 230 | 231 | instance.Encounters.Add(encObj.Name, encObj); 232 | Logger.Log(LogLevel.Info, 233 | string.Format("Parsed Encounter: {0} From Instance: {1}.", encObj.Name, 234 | instance.MapName)); 235 | } 236 | } 237 | 238 | return await Task.FromResult(true); 239 | } 240 | catch (InvalidOperationException invalidOperation) 241 | { 242 | Logger.Log(LogLevel.Fatal, "Invalid Operation Occurred, Response Stream for Instances/Zones is null."); 243 | Logger.Log(LogLevel.Fatal, invalidOperation.ToString()); 244 | return await Task.FromResult(false); 245 | } 246 | catch (Exception ex) 247 | { 248 | Logger.Log(LogLevel.Fatal, ex.ToString()); 249 | return await Task.FromResult(false); 250 | } 251 | } 252 | 253 | public static async Task BuildPercentiles() 254 | { 255 | try 256 | { 257 | foreach (var job in jobs) 258 | foreach (var instance in instances) 259 | foreach (var enc in instance.Encounters.Values) 260 | { 261 | if (enc.Category == 2) continue; 262 | var ranking = await GetRankingData(job, enc, APIKey); 263 | if (ranking == false) 264 | { 265 | Logger.Log(LogLevel.Error, 266 | "Failed Result: " + job.Name + " Encounter " + enc.Name); 267 | return await Task.FromResult(false); 268 | } 269 | } 270 | } 271 | catch (Exception ex) 272 | { 273 | return await Task.FromResult(false); 274 | } 275 | 276 | percentileData.LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); 277 | return await Task.FromResult(true); 278 | } 279 | 280 | public static async Task GetRankingData(ClassJob job, Encounter enc, string apiKey) 281 | { 282 | try 283 | { 284 | Logger.Log(LogLevel.Info, 285 | "Starting Parse Data For: " + job.Name + " Encounter " + enc.Name); 286 | 287 | var currentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); 288 | var startTime = percentileData.LastUpdated != 0 289 | ? percentileData.LastUpdated 290 | : new DateTimeOffset(DateTime.Now.Subtract(TimeSpan.FromDays(500))).ToUnixTimeMilliseconds(); 291 | var rankings = new List(); 292 | var hasMorePages = true; 293 | var page = 1; 294 | while (hasMorePages) 295 | { 296 | var request = WebRequest.Create(string.Format( 297 | "https://www.fflogs.com:443/v1/rankings/encounter/{0}?spec={1}&page={2}&filter=date.{3}.{4}&api_key=" + 298 | apiKey, 299 | enc.Key, job.Key, page, startTime, currentTime)); 300 | request.Timeout = 5000; 301 | 302 | var response = await request.GetResponseAsync(); 303 | using (var reader = new StreamReader(response.GetResponseStream())) 304 | { 305 | var json = reader.ReadToEnd(); 306 | var rankingObj = JObject.Parse(json); 307 | 308 | if (rankingObj.HasValues) 309 | { 310 | var count = rankingObj["count"].ToObject(); 311 | hasMorePages = rankingObj["hasMorePages"].ToObject(); 312 | var rankingArray = rankingObj["rankings"].ToObject(); 313 | if (rankingArray.HasValues) 314 | foreach (var ranking in rankingArray) 315 | rankings.Add(ranking["total"].ToObject()); 316 | } 317 | } 318 | 319 | Thread.Sleep(250); 320 | Logger.Log(LogLevel.Info, "Successfully Read Page: " + page); 321 | ++page; 322 | } 323 | 324 | Logger.Log(LogLevel.Info, "Successfully Read " + rankings.Count + " Rankings."); 325 | 326 | var name = enc.Name.ToLower(); 327 | if (percentileData.Rankings.ContainsKey(name) != true) 328 | { 329 | percentileData.Rankings.Add(name, new Dictionary>()); 330 | if (percentileData.Rankings[name].ContainsKey(job.Abbrveiation) != true) 331 | percentileData.Rankings[name].Add(job.Abbrveiation, new List()); 332 | } 333 | 334 | percentileData.Rankings[name][job.Abbrveiation].AddRange(rankings); 335 | Logger.Log(LogLevel.Info, "Success For Job " + job.Name + " Encounter " + enc.Name); 336 | return await Task.FromResult(true); 337 | } 338 | catch (WebException ex) 339 | { 340 | Logger.Log(LogLevel.Warn, ex.Message); 341 | Logger.Log(LogLevel.Warn, "No Data Exists for that Encounter and Job."); 342 | return await Task.FromResult(true); 343 | } 344 | } 345 | } 346 | } -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Parser/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PercentilePlugin.Parser")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PercentilePlugin.Parser")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7d0a1b52-778b-4897-91bb-fb4967439a66")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Parser/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Shared/ClassJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace PercentilePlugin.Shared 12 | { 13 | public class ClassJob 14 | { 15 | public string Name { get; set; } 16 | public string Abbrveiation { get; set; } 17 | public int Key { get; set; } 18 | 19 | public static string NameToAbbr(string name) 20 | { 21 | switch (name) 22 | { 23 | case "Astrologian": 24 | return "AST"; 25 | case "Bard": 26 | return "BRD"; 27 | case "Black Mage": 28 | return "BLM"; 29 | case "Dark Knight": 30 | return "DRK"; 31 | case "Dragoon": 32 | return "DRG"; 33 | case "Machinist": 34 | return "MCH"; 35 | case "Monk": 36 | return "MNK"; 37 | case "Ninja": 38 | return "NIN"; 39 | case "Paladin": 40 | return "PLD"; 41 | case "Scholar": 42 | return "SCH"; 43 | case "Summoner": 44 | return "SMN"; 45 | case "Warrior": 46 | return "WAR"; 47 | case "White Mage": 48 | return "WHM"; 49 | case "Red Mage": 50 | return "RDM"; 51 | case "Samurai": 52 | return "SAM"; 53 | default: 54 | return "???"; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Shared/DateTimeExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PercentilePlugin.Shared 8 | { 9 | public static class DateTimeExtension 10 | { 11 | /// 12 | /// Converts a given DateTime into a Unix timestamp 13 | /// 14 | /// Any DateTime 15 | /// The given DateTime in Unix timestamp format 16 | public static long ToUnixTimestamp(this DateTime value) 17 | { 18 | return (long)Math.Truncate((value.ToUniversalTime().Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds); 19 | } 20 | 21 | /// 22 | /// Gets a Unix timestamp representing the current moment 23 | /// 24 | /// Parameter ignored 25 | /// Now expressed as a Unix timestamp 26 | public static long UnixTimestamp(this DateTime ignored) 27 | { 28 | return (long)Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Shared/Encounter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace PercentilePlugin.Shared 12 | { 13 | public class Encounter 14 | { 15 | public string Name { get; set; } 16 | public string LastUpdated { get; set; } 17 | public int Key { get; set; } 18 | public int Category { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Shared/Instance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Linq; 11 | 12 | namespace PercentilePlugin.Shared 13 | { 14 | public class Instance 15 | { 16 | public Dictionary Encounters { get; set; } = new Dictionary(); 17 | 18 | public string MapName { get; set; } = ""; 19 | 20 | public static string InstanceFromBoss(string boss) 21 | { 22 | switch (boss) 23 | { 24 | case "Famfrit, the Darkening Cloud": 25 | case "Belias, the Gigas": 26 | case "Construct 7": 27 | case "Yiazmat": 28 | return "The Ridorana Lighthouse"; 29 | break; 30 | case "Mateus, the Corrupt": 31 | case "Hashmal, Bringer of Order": 32 | case "Rofocale": 33 | case "Argath Thadalfus": 34 | return "The Royal City of Rabanastre"; 35 | break; 36 | case "Phantom Train (Savage)": 37 | return "Sigmascape V1.0 (Savage)"; 38 | break; 39 | case "Demon Chadarnook (Savage)": 40 | return "Sigmascape V2.0 (Savage)"; 41 | break; 42 | case "Guardian (Savage)": 43 | return "Sigmascape V3.0 (Savage)"; 44 | break; 45 | case "Kefka (Savage)": 46 | case "God Kefka (Savage)": 47 | return "Sigmascape V4.0 (Savage)"; 48 | break; 49 | case "Phantom Train": 50 | return "Sigmascape (V1.0)"; 51 | break; 52 | case "Demon Chadarnook": 53 | return "Sigmascape (V2.0)"; 54 | break; 55 | case "Guardian": 56 | return "Sigmascape (V3.0)"; 57 | break; 58 | case "Kefka": 59 | return "Sigmascape (V4.0)"; 60 | break; 61 | case "Susano": 62 | return "The Pool of Tribute (Extreme)"; 63 | case "Lakshmi": 64 | return "Emanation (Extreme)"; 65 | break; 66 | case "Shinryu": 67 | return "The Minstrel's Ballad: Shinryu's Domain"; 68 | break; 69 | case "Byakko": 70 | return "The Jade Stoa (Extreme)"; 71 | break; 72 | case "Tsukuyomi": 73 | return "The Minstrel's Ballad: Tsukuyomi's Pain"; 74 | break; 75 | case "Suzaku": 76 | return "Hell's Kier (Extreme)"; 77 | case "Bahamut Prime": 78 | return "The Unending Coil of Bahamut"; 79 | break; 80 | case "The Ultima Weapon": 81 | return "Ultimacy"; 82 | break; 83 | case "Chaos": 84 | return "Alphascape (V1.0)"; 85 | break; 86 | case "Midgardsormr": 87 | return "Alphascape (V2.0)"; 88 | break; 89 | case "Omega": 90 | return "Alphascape (V3.0)"; 91 | break; 92 | case "Omega-M and Omega-F": 93 | return "Alphascape (V4.0)"; 94 | break; 95 | case "Omega-M and Omega-F (Savage)": 96 | case "The Final Omega (Savage)": 97 | return "Alphascape V4.0 (Savage)"; 98 | case "Chaos (Savage)": 99 | return "Alphascape V1.0 (Savage)"; 100 | break; 101 | case "Midgardsormr (Savage)": 102 | return "Alphascape V2.0 (Savage)"; 103 | break; 104 | case "Omega (Savage)": 105 | return "Alphascape V3.0 (Savage)"; 106 | break; 107 | } 108 | 109 | return ""; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Shared/PercentileData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Bson; 5 | 6 | namespace PercentilePlugin.Shared 7 | { 8 | public class PercentileData 9 | { 10 | public long LastUpdated { get; set; } = 0; 11 | 12 | public Dictionary>> Rankings { get; set; } = 13 | new Dictionary>>(); 14 | 15 | public static PercentileData Load(string file) 16 | { 17 | // Return new instance if file does not exists 18 | if (File.Exists(file) != true) return new PercentileData(); 19 | 20 | var fileStream = new FileStream(file, FileMode.OpenOrCreate); 21 | using (var reader = new BsonReader(fileStream)) 22 | { 23 | var serializer = new JsonSerializer(); 24 | return serializer.Deserialize(reader); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Shared/PercentilePlugin.Shared.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {76C3FAF6-98DD-48C1-808B-30A9F879E5A7} 8 | Library 9 | Properties 10 | PercentilePlugin.Shared 11 | PercentilePlugin.Shared 12 | v4.5.1 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Designer 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Shared/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PercentilePlugin.Shared")] 9 | [assembly: AssemblyDescription("Shared data for the PercentileData Parser and FFXIV Plugin.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PercentilePlugin.Shared")] 13 | [assembly: AssemblyCopyright("Copyright © Liquidize 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("76c3faf6-98dd-48c1-808b-30a9f879e5a7")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.Shared/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2037 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PercentilePlugin", "PercentilePlugin\PercentilePlugin.csproj", "{BEA5D2A7-AA3C-4BC8-9227-03B80A848ADE}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PercentilePlugin.Parser", "PercentilePlugin.Parser\PercentilePlugin.Parser.csproj", "{7D0A1B52-778B-4897-91BB-FB4967439A66}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PercentilePlugin.Shared", "PercentilePlugin.Shared\PercentilePlugin.Shared.csproj", "{76C3FAF6-98DD-48C1-808B-30A9F879E5A7}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {BEA5D2A7-AA3C-4BC8-9227-03B80A848ADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {BEA5D2A7-AA3C-4BC8-9227-03B80A848ADE}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {BEA5D2A7-AA3C-4BC8-9227-03B80A848ADE}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {BEA5D2A7-AA3C-4BC8-9227-03B80A848ADE}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {7D0A1B52-778B-4897-91BB-FB4967439A66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {7D0A1B52-778B-4897-91BB-FB4967439A66}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {7D0A1B52-778B-4897-91BB-FB4967439A66}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {7D0A1B52-778B-4897-91BB-FB4967439A66}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {76C3FAF6-98DD-48C1-808B-30A9F879E5A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {76C3FAF6-98DD-48C1-808B-30A9F879E5A7}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {76C3FAF6-98DD-48C1-808B-30A9F879E5A7}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {76C3FAF6-98DD-48C1-808B-30A9F879E5A7}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {98CB1CAC-5D17-4EDE-83C8-283EB87020E2} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace PercentilePlugin 9 | { 10 | /// 11 | /// Logger 12 | /// 13 | public class Logger 14 | { 15 | /// 16 | /// Log Entries 17 | /// 18 | public BindingList Logs { get; private set; } 19 | 20 | public Logger() 21 | { 22 | this.Logs = new BindingList(); 23 | } 24 | 25 | public void Log(LogLevel level, string message) 26 | { 27 | #if !DEBUG 28 | if (level == LogLevel.Trace || level == LogLevel.Debug) 29 | { 30 | return; 31 | } 32 | #endif 33 | #if DEBUG 34 | System.Diagnostics.Trace.WriteLine(string.Format("{0}: {1}: {2}", level, DateTime.Now, message)); 35 | #endif 36 | 37 | this.Logs.Add(new LogEntry(level, DateTime.Now, message)); 38 | } 39 | 40 | public void Log(LogLevel level, string format, params object[] args) 41 | { 42 | Log(level, string.Format(format, args)); 43 | } 44 | } 45 | 46 | public class LogEntry 47 | { 48 | public string Message { get; set; } 49 | public LogLevel Level { get; set; } 50 | public DateTime Time { get; set; } 51 | 52 | public LogEntry(LogLevel level, DateTime time, string message) 53 | { 54 | this.Message = message; 55 | this.Level = level; 56 | this.Time = time; 57 | } 58 | } 59 | 60 | public class LogEventArgs : EventArgs 61 | { 62 | public string Message { get; private set; } 63 | public LogLevel Level { get; private set; } 64 | public LogEventArgs(LogLevel level, string message) 65 | { 66 | this.Message = message; 67 | this.Level = level; 68 | } 69 | } 70 | 71 | public enum LogLevel 72 | { 73 | Trace, 74 | Debug, 75 | Info, 76 | Warning, 77 | Error 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/Options.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Newtonsoft.Json; 3 | 4 | namespace PercentilePlugin 5 | { 6 | public class Options 7 | { 8 | public static Options Instance { get; private set; } 9 | 10 | public string RemoteVersionSeen { get; set; } = "0.0.0"; 11 | 12 | public bool AutoUpdate { get; set; } = false; 13 | 14 | public void Save() 15 | { 16 | if (Directory.Exists("PercentilePlugin") != true) Directory.CreateDirectory("PercentilePlugin"); 17 | File.WriteAllText("PercentilePlugin/options.json", JsonConvert.SerializeObject(this, Formatting.Indented)); 18 | } 19 | 20 | public static void Load() 21 | { 22 | if (File.Exists("PercentilePlugin/options.json")) 23 | Instance = JsonConvert.DeserializeObject(File.ReadAllText("PercentilePlugin/options.json")); 24 | else 25 | Instance = new Options(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/PercentilePlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Net; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | using Advanced_Combat_Tracker; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Bson; 11 | using PercentilePlugin.Shared; 12 | 13 | namespace PercentilePlugin 14 | { 15 | public class PercentilePlugin : IActPluginV1 16 | { 17 | private Label pluginLabel; 18 | public PercentileUi percentileUi; 19 | public static Logger Logger; 20 | public static PercentileData PercentileData; 21 | 22 | private static readonly int kRequiredNETVersionMajor = 4; 23 | private static readonly int kRequiredNETVersionMinor = 6; 24 | private static readonly int kRequiredNETVersionRevision = 1; 25 | 26 | public async void InitPlugin(TabPage pluginScreenSpace, Label pluginStatusText) 27 | { 28 | Logger = new Logger(); 29 | Logger.Log(LogLevel.Info, "Plugin Init."); 30 | 31 | 32 | if (Directory.Exists("PercentilePlugin") != true) 33 | { 34 | Logger.Log(LogLevel.Warning, "Creating PercentilePlugin Directory...."); 35 | Directory.CreateDirectory("PercentilePlugin"); 36 | } 37 | 38 | Options.Load(); 39 | Logger.Log(LogLevel.Info, "Loaded Options."); 40 | PercentileData = PercentileData.Load("PercentilePlugin/parsedata.bin"); 41 | Logger.Log(LogLevel.Info, "Percentile Data Loaded."); 42 | 43 | percentileUi = new PercentileUi(); 44 | pluginLabel = pluginStatusText; 45 | percentileUi.Dock = DockStyle.Fill; 46 | pluginScreenSpace.Controls.Add(percentileUi); 47 | 48 | CombatantData.ColumnDefs.Add("Percentile", new CombatantData.ColumnDef("Percentile", true, "FLOAT", 49 | "Percentile", 50 | Data => { return GetPercentile(Data).ToString(); }, 51 | Data => { return GetPercentile(Data).ToString(); }, 52 | (Left, Right) => { return GetPercentile(Left).CompareTo(GetPercentile(Right)); })); 53 | ActGlobals.oFormActMain.ValidateTableSetup(); 54 | CombatantData.ExportVariables.Add("Percentile", new CombatantData.TextExportFormatter("percentile", 55 | "Percentile", "2 Week Historical Percentile based off current DPS.", 56 | (Data, Extra) => { return GetPercentile(Data).ToString(); })); 57 | ActGlobals.oFormActMain.ValidateLists(); 58 | Logger.Log(LogLevel.Info, "Percentile Column Added."); 59 | pluginStatusText.Text = "Plugin Loaded."; 60 | percentileUi.lastCheckLbl.Text = 61 | "Last Updated: " + new DateTime(1970, 1, 1).AddMilliseconds(PercentileData.LastUpdated).ToLocalTime() 62 | .ToString("F"); 63 | Logger.Log(LogLevel.Info, "Plugin Loaded"); 64 | 65 | var checker = new VersionChecker(); 66 | var local = checker.GetLocalVersion(); 67 | var remote = checker.GetRemoteVersion(); 68 | if (remote.Major == 0 && remote.Minor == 0) 69 | { 70 | var result = MessageBox.Show( 71 | "Github error while checking PercentilePlugin version. " + 72 | "Your current version is " + local + ".\n\n" + 73 | "Manually check for newer version now?", 74 | "PercentilePlugin Manual Check", 75 | MessageBoxButtons.YesNo); 76 | if (result == DialogResult.Yes) 77 | Process.Start(VersionChecker.kReleaseUrl); 78 | } 79 | else if (local < remote) 80 | { 81 | if (Options.Instance != null && string.IsNullOrEmpty(Options.Instance.RemoteVersionSeen)) 82 | Options.Instance.RemoteVersionSeen = "0.0.0"; 83 | 84 | var remote_seen_before = new Version(Options.Instance.RemoteVersionSeen); 85 | Options.Instance.RemoteVersionSeen = remote.ToString(); 86 | 87 | var update_message = "There is a new version of PercentilePlugin is available at: \n" + 88 | VersionChecker.kReleaseUrl + " \n\n" + 89 | "New version " + remote + " \n" + 90 | "Current version " + local; 91 | if (remote == remote_seen_before) 92 | { 93 | Logger.Log(LogLevel.Warning, "Remote Version Seen Before."); 94 | } 95 | else 96 | { 97 | var result = MessageBox.Show( 98 | update_message + "\n\n" + 99 | "Get it now?", 100 | "PercentilePlugin update available", 101 | MessageBoxButtons.YesNo); 102 | if (result == DialogResult.Yes) 103 | Process.Start(VersionChecker.kReleaseUrl); 104 | } 105 | 106 | var net_version_str = FileVersionInfo 107 | .GetVersionInfo(typeof(int).Assembly.Location).ProductVersion; 108 | var net_version = net_version_str.Split('.'); 109 | if (int.Parse(net_version[0]) < kRequiredNETVersionMajor || 110 | int.Parse(net_version[1]) < kRequiredNETVersionMinor || 111 | int.Parse(net_version[2]) < kRequiredNETVersionRevision) 112 | Logger.Log(LogLevel.Error, "Requires .NET 4.5.1 or above. Using " + net_version_str); 113 | 114 | 115 | Options.Instance.RemoteVersionSeen = remote.ToString(); 116 | Options.Instance.Save(); 117 | } 118 | 119 | if (Options.Instance.AutoUpdate) 120 | { 121 | var backgroundWorker = new BackgroundWorker(); 122 | backgroundWorker.DoWork += (sender, args) => { UpdateData(); }; 123 | backgroundWorker.RunWorkerAsync(); 124 | } 125 | } 126 | 127 | public async Task UpdateData() 128 | { 129 | try 130 | { 131 | var request = 132 | WebRequest.Create("https://github.com/Liquidize/FFXIV_PercentilePlugin/raw/master/parsedata.bin"); 133 | var response = await request.GetResponseAsync(); 134 | if (response.GetResponseStream() != null) 135 | using (var bsonReader = new BsonReader(response.GetResponseStream())) 136 | { 137 | var serializer = new JsonSerializer(); 138 | var data = serializer.Deserialize(bsonReader); 139 | if (data != null) 140 | { 141 | PercentileData = data; 142 | var file = new FileStream("PercentilePlugin/parsedata.bin", FileMode.OpenOrCreate); 143 | using (var writer = new BsonWriter(file)) 144 | { 145 | serializer.Serialize(writer, data); 146 | } 147 | 148 | file.Close(); 149 | Logger.Log(LogLevel.Info, "Percentile Data has been updated."); 150 | percentileUi.UpdateLabelText(); 151 | } 152 | else 153 | { 154 | MessageBox.Show("Error in updating data."); 155 | } 156 | } 157 | } 158 | catch (WebException webEx) 159 | { 160 | Logger.Log(LogLevel.Error, "Error in updating data."); 161 | Logger.Log(LogLevel.Error, webEx.ToString()); 162 | } 163 | catch (JsonException jsonEx) 164 | { 165 | Logger.Log(LogLevel.Error, "Error in updating data."); 166 | Logger.Log(LogLevel.Error, jsonEx.ToString()); 167 | } 168 | } 169 | 170 | public void DeInitPlugin() 171 | { 172 | pluginLabel.Text = "Plugin Exited."; 173 | } 174 | 175 | public string GetTranslatedStrongest(string boss) 176 | { 177 | switch (boss) 178 | { 179 | case "カオス": 180 | return "Chaos"; 181 | case "ミドガルズオルム": 182 | return "Midgardsormr"; 183 | case "オメガ": 184 | return "Omega"; 185 | case "朱雀": 186 | return "Suzaku"; 187 | case "ツクヨミ": 188 | return "Tsukuyomi"; 189 | case "白虎": 190 | return "Byakko"; 191 | case "神龍": 192 | return "Shinryu"; 193 | case "ラクシュミ": 194 | return "Lakshmi"; 195 | case "スサノオ": 196 | return "Susano"; 197 | case "背徳の皇帝マティウス": 198 | return "Mateus, the Corrupt"; 199 | case "統制者ハシュマリム": 200 | return "Hashmal, Bringer of Order"; 201 | case "人馬王ロフォカレ": 202 | return "Rofocale"; 203 | case "冷血剣アルガス": 204 | return "Argath Thadalfus"; 205 | case "暗黒の雲ファムフリート": 206 | return "Famfrit, the Darkening Cloud"; 207 | case "魔人ベリアス": 208 | return "Belias, the Gigas"; 209 | case "労働七号": 210 | return "Construct 7"; 211 | case "鬼龍ヤズマット": 212 | return "Yiazmat"; 213 | } 214 | 215 | return ""; 216 | } 217 | 218 | public string GetBoss(EncounterData enc) 219 | { 220 | var strongest = enc.GetStrongestEnemy("YOU"); 221 | var combatant = enc.GetCombatant(strongest); 222 | strongest = string.IsNullOrEmpty(GetTranslatedStrongest(strongest)) 223 | ? strongest 224 | : GetTranslatedStrongest(strongest); 225 | if (enc.ZoneName.ToLower() == "the Weapon's Refrain (Ultimate)".ToLower()) return "The Ultima Weapon"; 226 | if (enc.ZoneName.ToLower() == "the Unending Coil of Bahamut (Ultimate)".ToLower()) return "Bahamut Prime"; 227 | 228 | if (enc.ZoneName.Contains("Alphascape (V4.0)")) strongest = "Omega-M and Omega-F"; 229 | 230 | if (enc.ZoneName.Contains("Savage")) strongest = strongest + " (Savage)"; 231 | 232 | if (enc.ZoneName.Contains("Alphascape") && enc.ZoneName.Contains("4.0") && enc.ZoneName.Contains("Savage")) 233 | { 234 | if (combatant.AllOut.ContainsKey("Target Analysis") || combatant.AllOut.ContainsKey("標的識別") || 235 | combatant.AllOut.ContainsKey("Unknown_336C")) 236 | return "The Final Omega (Savage)"; 237 | return "Omega-M and Omega-F (Savage)"; 238 | } 239 | 240 | return strongest; 241 | } 242 | 243 | public double GetPercentile(CombatantData combatant) 244 | { 245 | var job = combatant.GetColumnByName("Job").ToUpper(); 246 | 247 | if (combatant.Parent == null) return -1; 248 | if (string.IsNullOrEmpty(job)) return -1; 249 | 250 | var boss = GetBoss(combatant.Parent).ToLower(); 251 | 252 | if (!string.IsNullOrEmpty(boss)) return GetRealPercentile(job, boss, combatant.EncDPS); 253 | 254 | return -1; 255 | } 256 | 257 | 258 | public static double GetRealPercentile(string job, string enc, double DPS) 259 | { 260 | if (PercentileData.Rankings.ContainsKey(enc) != true) return 0; 261 | var sequence = PercentileData.Rankings[enc][job].ToArray(); 262 | if (sequence.Length == 0) return 100; 263 | Array.Sort(sequence); 264 | var l = 0; 265 | var r = sequence.Length - 1; 266 | var index = sequence.Length / 2; 267 | 268 | while (l <= r) 269 | { 270 | index = l + (r - l) / 2; 271 | 272 | if (sequence[index] < DPS) 273 | l = index + 1; 274 | else 275 | r = index - 1; 276 | } 277 | 278 | return (100 * index + sequence.Length) / sequence.Length; 279 | } 280 | } 281 | } -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/PercentilePlugin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BEA5D2A7-AA3C-4BC8-9227-03B80A848ADE} 8 | Library 9 | Properties 10 | PercentilePlugin 11 | PercentilePlugin 12 | v4.5.1 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | C:\Program Files (x86)\Advanced Combat Tracker\Advanced Combat Tracker.exe 36 | 37 | 38 | ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | UserControl 58 | 59 | 60 | PercentileUi.cs 61 | 62 | 63 | 64 | 65 | 66 | 67 | PercentileUi.cs 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {76c3faf6-98dd-48c1-808b-30a9f879e5a7} 77 | PercentilePlugin.Shared 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/PercentileUi.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PercentilePlugin 2 | { 3 | partial class PercentileUi 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.logView = new System.Windows.Forms.ListView(); 32 | this.timeCol = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 33 | this.levelCol = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 34 | this.messageCol = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 35 | this.lastCheckLbl = new System.Windows.Forms.Label(); 36 | this.updateBtn = new System.Windows.Forms.Button(); 37 | this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); 38 | this.autoUpdateChbx = new System.Windows.Forms.CheckBox(); 39 | this.SuspendLayout(); 40 | // 41 | // logView 42 | // 43 | this.logView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 44 | this.timeCol, 45 | this.levelCol, 46 | this.messageCol}); 47 | this.logView.Dock = System.Windows.Forms.DockStyle.Bottom; 48 | this.logView.Location = new System.Drawing.Point(0, 93); 49 | this.logView.Name = "logView"; 50 | this.logView.Size = new System.Drawing.Size(906, 432); 51 | this.logView.TabIndex = 0; 52 | this.logView.UseCompatibleStateImageBehavior = false; 53 | this.logView.View = System.Windows.Forms.View.Details; 54 | this.logView.VirtualMode = true; 55 | this.logView.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.logView_RetrieveVirtualItem); 56 | // 57 | // timeCol 58 | // 59 | this.timeCol.Text = "Time"; 60 | this.timeCol.Width = 106; 61 | // 62 | // levelCol 63 | // 64 | this.levelCol.Text = "Level"; 65 | // 66 | // messageCol 67 | // 68 | this.messageCol.Text = "Message"; 69 | this.messageCol.Width = 736; 70 | // 71 | // lastCheckLbl 72 | // 73 | this.lastCheckLbl.AutoSize = true; 74 | this.lastCheckLbl.Location = new System.Drawing.Point(3, 5); 75 | this.lastCheckLbl.Name = "lastCheckLbl"; 76 | this.lastCheckLbl.Size = new System.Drawing.Size(160, 13); 77 | this.lastCheckLbl.TabIndex = 1; 78 | this.lastCheckLbl.Text = "Last Updated: NOT-AVAILABLE"; 79 | // 80 | // updateBtn 81 | // 82 | this.updateBtn.Location = new System.Drawing.Point(3, 21); 83 | this.updateBtn.Name = "updateBtn"; 84 | this.updateBtn.Size = new System.Drawing.Size(75, 23); 85 | this.updateBtn.TabIndex = 3; 86 | this.updateBtn.Text = "Update"; 87 | this.updateBtn.UseVisualStyleBackColor = true; 88 | this.updateBtn.Click += new System.EventHandler(this.updateBtn_Click); 89 | // 90 | // autoUpdateChbx 91 | // 92 | this.autoUpdateChbx.AutoSize = true; 93 | this.autoUpdateChbx.Location = new System.Drawing.Point(85, 26); 94 | this.autoUpdateChbx.Name = "autoUpdateChbx"; 95 | this.autoUpdateChbx.Size = new System.Drawing.Size(134, 17); 96 | this.autoUpdateChbx.TabIndex = 6; 97 | this.autoUpdateChbx.Text = "Auto Update On Start?"; 98 | this.autoUpdateChbx.UseVisualStyleBackColor = true; 99 | this.autoUpdateChbx.CheckedChanged += new System.EventHandler(this.autoUpdateChbx_CheckedChanged); 100 | // 101 | // PercentileUi 102 | // 103 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 104 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 105 | this.AutoSize = true; 106 | this.Controls.Add(this.autoUpdateChbx); 107 | this.Controls.Add(this.updateBtn); 108 | this.Controls.Add(this.lastCheckLbl); 109 | this.Controls.Add(this.logView); 110 | this.Name = "PercentileUi"; 111 | this.Size = new System.Drawing.Size(906, 525); 112 | this.ResumeLayout(false); 113 | this.PerformLayout(); 114 | 115 | } 116 | 117 | #endregion 118 | 119 | private System.Windows.Forms.ListView logView; 120 | private System.Windows.Forms.ColumnHeader timeCol; 121 | private System.Windows.Forms.ColumnHeader levelCol; 122 | private System.Windows.Forms.ColumnHeader messageCol; 123 | private System.Windows.Forms.Button updateBtn; 124 | private System.ComponentModel.BackgroundWorker backgroundWorker1; 125 | public System.Windows.Forms.Label lastCheckLbl; 126 | private System.Windows.Forms.CheckBox autoUpdateChbx; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/PercentileUi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.IO; 4 | using System.Net; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Bson; 10 | using PercentilePlugin.Shared; 11 | 12 | namespace PercentilePlugin 13 | { 14 | public partial class PercentileUi : UserControl 15 | { 16 | private CancellationTokenSource cts; 17 | private Task updateTask; 18 | 19 | public PercentileUi() 20 | { 21 | InitializeComponent(); 22 | logView.VirtualListSize = PercentilePlugin.Logger.Logs.Count; 23 | PercentilePlugin.Logger.Logs.ListChanged += (o, e) => 24 | { 25 | logView.BeginUpdate(); 26 | logView.VirtualListSize = PercentilePlugin.Logger.Logs.Count; 27 | logView.EnsureVisible(logView.VirtualListSize - 1); 28 | logView.EndUpdate(); 29 | }; 30 | autoUpdateChbx.Checked = Options.Instance.AutoUpdate; 31 | } 32 | 33 | private void logView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e) 34 | { 35 | if (e.ItemIndex >= PercentilePlugin.Logger.Logs.Count) 36 | { 37 | e.Item = new ListViewItem(); 38 | return; 39 | } 40 | 41 | var log = PercentilePlugin.Logger.Logs[e.ItemIndex]; 42 | e.Item = new ListViewItem(log.Time.ToString()); 43 | e.Item.UseItemStyleForSubItems = true; 44 | e.Item.SubItems.Add(log.Level.ToString()); 45 | e.Item.SubItems.Add(log.Message); 46 | e.Item.ForeColor = Color.Black; 47 | if (log.Level == LogLevel.Warning) 48 | e.Item.BackColor = Color.LightYellow; 49 | else if (log.Level == LogLevel.Error) 50 | e.Item.BackColor = Color.LightPink; 51 | else 52 | e.Item.BackColor = Color.White; 53 | } 54 | 55 | public void UpdateLabelText() 56 | { 57 | lastCheckLbl.Text = "Last Updated: " + new DateTime(1970, 1, 1) 58 | .AddMilliseconds(PercentilePlugin.PercentileData.LastUpdated).ToLocalTime() 59 | .ToString("F"); 60 | } 61 | 62 | private async void updateBtn_Click(object sender, EventArgs e) 63 | { 64 | updateBtn.Enabled = false; 65 | try 66 | { 67 | var request = 68 | WebRequest.Create("https://github.com/Liquidize/FFXIV_PercentilePlugin/raw/master/parsedata.bin"); 69 | var response = await request.GetResponseAsync(); 70 | if (response.GetResponseStream() != null) 71 | using (var bsonReader = new BsonReader(response.GetResponseStream())) 72 | { 73 | var serializer = new JsonSerializer(); 74 | var data = serializer.Deserialize(bsonReader); 75 | if (data != null) 76 | { 77 | PercentilePlugin.PercentileData = data; 78 | var file = new FileStream("PercentilePlugin/parsedata.bin", FileMode.OpenOrCreate); 79 | using (var writer = new BsonWriter(file)) 80 | { 81 | serializer.Serialize(writer, data); 82 | } 83 | 84 | file.Close(); 85 | PercentilePlugin.Logger.Log(LogLevel.Info, "Percentile Data has been updated."); 86 | } 87 | else 88 | { 89 | MessageBox.Show("Error in updating data."); 90 | } 91 | } 92 | } 93 | catch (WebException webEx) 94 | { 95 | PercentilePlugin.Logger.Log(LogLevel.Error, "Error in updating data."); 96 | PercentilePlugin.Logger.Log(LogLevel.Error, webEx.ToString()); 97 | } 98 | catch (JsonException jsonEx) 99 | { 100 | PercentilePlugin.Logger.Log(LogLevel.Error, "Error in updating data."); 101 | PercentilePlugin.Logger.Log(LogLevel.Error, jsonEx.ToString()); 102 | } 103 | 104 | lastCheckLbl.Text = "Last Updated: " + new DateTime(1970, 1, 1) 105 | .AddMilliseconds(PercentilePlugin.PercentileData.LastUpdated).ToLocalTime() 106 | .ToString("F"); 107 | updateBtn.Enabled = true; 108 | } 109 | 110 | private void autoUpdateChbx_CheckedChanged(object sender, EventArgs e) 111 | { 112 | Options.Instance.AutoUpdate = autoUpdateChbx.Checked; 113 | Options.Instance.Save(); 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/PercentileUi.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Percentile Plugin")] 9 | [assembly: AssemblyDescription("Allows overlays to display real time FFLogs percentiles.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PercentilePlugin")] 13 | [assembly: AssemblyCopyright("Copyright © Liquidize 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("bea5d2a7-aa3c-4bc8-9227-03b80a848ade")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.2.5.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/VersionChecker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | using Advanced_Combat_Tracker; 8 | 9 | namespace PercentilePlugin 10 | { 11 | // This class can determine the current plugin version, as well as the latest version 12 | // released of the plugin on GitHub. It is inspired by the work of anoyetta in 13 | // https://github.com/anoyetta/ACT.SpecialSpellTimer/blob/master/ACT.SpecialSpellTimer.Core/UpdateChecker.cs 14 | public class VersionChecker 15 | { 16 | public const string kReleaseUrl = "https://github.com/Liquidize/FFXIV_PercentilePlugin/releases/latest"; 17 | public const string kIssueUrl = "https://github.com/Liquidize/FFXIV_PercentilePlugin/releases/issues"; 18 | 19 | public VersionChecker() 20 | { 21 | } 22 | 23 | public Version GetLocalVersion() 24 | { 25 | return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; 26 | } 27 | 28 | public string GetLocalLocation() 29 | { 30 | return System.Reflection.Assembly.GetExecutingAssembly().Location; 31 | } 32 | 33 | public Version GetACTVersion() 34 | { 35 | return System.Reflection.Assembly.GetAssembly(typeof(Advanced_Combat_Tracker.ActGlobals)).GetName().Version; 36 | } 37 | 38 | public string GetACTLocation() 39 | { 40 | return System.Reflection.Assembly.GetAssembly(typeof(Advanced_Combat_Tracker.ActGlobals)).Location; 41 | } 42 | 43 | public Version GetRemoteVersion() 44 | { 45 | string html; 46 | try 47 | { 48 | var web = new System.Net.WebClient(); 49 | System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12; 50 | var page_stream = web.OpenRead(kReleaseUrl); 51 | var reader = new System.IO.StreamReader(page_stream); 52 | html = reader.ReadToEnd(); 53 | } 54 | catch (Exception e) 55 | { 56 | PercentilePlugin.Logger.Log(LogLevel.Error,"Error fetching most recent github release: " + e.Message + "\n" + e.StackTrace); 57 | return new Version(); 58 | } 59 | 60 | var pattern = @"href=""/liquidize/FFXIV_PercentilePlugin/releases/download/v?(?.*?)/"; 61 | var regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); 62 | var match = regex.Match(html); 63 | if (!match.Success) 64 | { 65 | PercentilePlugin.Logger.Log(LogLevel.Error, "Error parsing most recent github release, no match found. Please report an issue at " + kIssueUrl); 66 | return new Version(); 67 | } 68 | 69 | string version_string = match.Groups["Version"].Value; 70 | 71 | pattern = @"(?(?[0-9]+)\.(?[0-9]+)\.(?[0-9+]))"; 72 | regex = new Regex(pattern); 73 | match = regex.Match(version_string); 74 | if (!match.Success) 75 | { 76 | PercentilePlugin.Logger.Log(LogLevel.Error, "Error parsing most recent github release, no version number found. Please report an issue at " + kIssueUrl); 77 | return new Version(); 78 | } 79 | 80 | return new Version(int.Parse(match.Groups["Major"].Value), int.Parse(match.Groups["Minor"].Value), int.Parse(match.Groups["Revision"].Value)); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /PercentilePlugin/PercentilePlugin/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FFXIV_PercentilePlugin (beta) 2 | Percentile Plugin for ACT to allow overlays to display FFLogs percentile data in real time. 3 | 4 | The plugin works by using data obtained from FFLogs and calculating the current percentile in real time using your current DPS. The data used should be fairly accurate +/- 1% (give or take) of the Historical Percentile if you use the latest data. You can view the percentiles in real time via the "Percentile" column that is added to ACT or via a compatible overlay. 5 | 6 | Percentiles displayed in a modified Kagerou overlay (the Pct column): 7 | ![Example](https://i.imgur.com/lrgGFzG.png) 8 | 9 | **Note:** The plugin is still in development and while fully functional for displaying percentiles for known fights, future fights and some current fights may need to be adjusted. 10 | 11 | # How-To 12 | 13 | 1. Obtain the latest version (zip file) of the plugin from the [Releases](https://github.com/Liquidize/FFXIV_PercentilePlugin/releases) 14 | 2. Extract the zip file to where your "Advanced Combat Tracker.exe" is, usually something like "C:\Program Files (x86)\Advanced Combat Tracker" 15 | 3. Run ACT as administrator (incase of permissions errors for writing config files) 16 | 4. Go to the "Plugins" tab and the "PercentilePlugin.dll" as a plugin. 17 | 5. Go to the "PercentilePlugin" plugin tab, and click update. 18 | 6. Use a compatible overlay if you want to see the information in the overlay. 19 | 20 | **Note:** I've forked and added percentile functionality to the popular [Kagerou](https://github.com/hibiyasleep/kagerou) overlay, simply change the overlay URL to: **https://liquidize.github.io/kagerou/overlay/** and you should be able to add "Percentile" as a column in your tabs in the config. You will need to completely reconfigure your overlay though, unfortunately. 21 | 22 | Clicking the "Update" button will download the "parsedata.bin" file that is stored in this repository. This file contains all the data needed to calculate your parses, and is updated daily. 23 | 24 | # Compatible Overlays 25 | 26 | 1. [Kagerou pct](https://github.com/Liquidize/kagerou) - Forked by me, this is the latest version of Kagerou with the added ability to add "Percentile" as a column to your tabs. Just like Kagerou you can easily set this as your overlay by using the following url: https://liquidize.github.io/kagerou/overlay/ 27 | 2. [Fancy Detail Overlay](https://i.imgur.com/HGSZoQ6.png) - By 라그린네, I am unsure of the original name of this overlay a user of the plugin asked me to add support for it. You can find the overlay in the Overlays folder on the repository. 28 | 3. [MopiMopi Pct](https://github.com/Liquidize/mopimopi) - A fork of [MopiMopi](https://github.com/HAERUHAERU/mopimopi) by [HaeruHaeru](https://github.com/HAERUHAERU/) with percentile support added by myself. This overlay requires ActWebSocket, follow the guide [HERE](https://docs.google.com/presentation/d/1U7-Vgv6UA2_EFdvw3m8BI-5-9T91WeKTflDuR7rEx-U/edit). Click [HERE](https://i.imgur.com/bPvNkIQ.png) for a preview. Use [https://liquidize.github.io/mopimopi/](https://liquidize.github.io/mopimopi/) as the URL instead of the one in the guide. 29 | 4. [Horizon Overlay](https://github.com/unfaiyted/horizoverlay) - The Horizon Overlay with Percentile support by [Unfaiyted](https://github.com/unfaiyted/). 30 | 31 | If you want your overlay listed here, please message me in game or on discord. 32 | 33 | * **Character in game:** Kaliya Y'mhitra (Goblin - NA) 34 | * **Discord:** Kaliya#0001 35 | 36 | # Credits 37 | 38 | * Liquidize / Kaliya Y'mhitra (Goblin - NA) - Developer/Plugin Creator 39 | * Kaliph Soren (Goblin - NA) - Co-developer 40 | * [Hibiyasleep](https://github.com/hibiyasleep) - Creator of the Kagerou overlay which I forked and added functionality to. 41 | * The update functionality of the plugin was taken from Cactbot an open source Raid helper found [here](https://github.com/quisquous/cactbot). 42 | -------------------------------------------------------------------------------- /parsedata.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liquidize/FFXIV_PercentilePlugin/611d147b8c828d3f45d1d84a9c88c322e51563c8/parsedata.bin --------------------------------------------------------------------------------