public void WriteJsonModel(IEnumerable <ILogSource> logSources, TextWriter writer) { var errors = new List <string>(); var jsonRotations = new List <JsonRotation>(); var usedSkills = new Dictionary <uint, string>(); foreach (var source in logSources) { IEnumerable <Rotation> rotations; try { rotations = source.GetRotations(); } catch (Exception e) { errors.Add($"Failed to process {source.GetLogName()}: " + e.Message); continue; } foreach (var rotation in rotations) { var player = new PlayerData(rotation.PlayerName, GetTinyProfessionIconUrl(rotation.Profession, rotation.Specialization), source.GetLogName(), source.GetEncounterName()); foreach (var skillCast in rotation.Items.OfType <SkillCast>()) { usedSkills[skillCast.SkillId] = skillCast.SkillName; } jsonRotations.Add(new JsonRotation(player, rotation.Items)); } } var skillData = usedSkills .Select(x => (Skill: (Id: x.Key, Name: x.Value), Data: apiData.GetSkillData((int)x.Key))) .ToDictionary( x => x.Skill.Id, x => x.Data == null ? new { Name = x.Skill.Name, IconUrl = (string)null } : new { Name = x.Data.Name, IconUrl = x.Data.IconUrl } ); writer.Write(JsonConvert.SerializeObject(new { Rotations = jsonRotations, SkillData = skillData, Errors = errors })); }
public override void WriteHtml(TextWriter writer) { foreach (var data in playerData) { var player = data.Player; // TODO: Proper alt for profession icon writer.WriteLine($@" <div class='box content'> <article class='media'> <div class='media-left'> <figure class='image is-64x64'> <img src='{Theme.GetBigProfessionIconUrl(player)}' alt='Specialization icon'> </figure>" ); foreach (var badge in data.Badges) { if (badge.Type == BadgeType.Specialization) { writer.WriteLine($"<span class='tag is-rounded player-trait-badge'>{badge.Text}</span>"); } } writer.WriteLine($@" </div> <div class='media-content'> <div class='content'> <p> <strong>{player.Name}</strong> <small>{player.AccountName.Substring(1)}</small> <small>(group {player.Subgroup})</small> </p> <p>" ); if (data.UtilitySkills != null && data.EliteSkills != null && data.HealingSkills != null) { var skillMatrix = GetSkillMatrix(data); for (int row = 0; row < skillMatrix.Length; row++) { foreach (var skillData in skillMatrix[row]) { if (skillData == null) { writer.WriteLine( "<img class='player-skill-image' src='https://wiki.guildwars2.com/images/7/74/Skill.png' alt='Unknown skill' title='Unknown skill, unused or instant cast'>"); } else { var encodedName = System.Web.HttpUtility.HtmlEncode(skillData.Name); writer.WriteLine( $"<img class='player-skill-image' src='{skillData.IconUrl}' alt='{encodedName}' title='{encodedName}'>"); } } if (row != skillMatrix.Length - 1) { writer.WriteLine("<br>"); } } } else { writer.WriteLine( $"No data on utility skills, perhaps API data was missing."); // TODO: Add reasons for missing data } writer.WriteLine($@" </p> </div> <div class='content is-hidden'>" ); // TODO: Tabs? if (data.Rotation != null) { foreach (var rotationItem in data.Rotation.Items.OrderBy(x => x.ItemTime)) { if (rotationItem is SkillCastItem skillCast) { var encodedName = HttpUtility.HtmlEncode(skillCast.Skill.Name); var statusClass = skillCast.Type == SkillCastType.Cancel ? "cancel" : skillCast.Type == SkillCastType.Reset ? "interrupt" : ""; var skillData = gw2ApiData?.GetSkillData(skillCast.Skill); if (skillData == null) { string title = $"No API data for skill {encodedName} ({skillCast.Skill.Id})"; writer.WriteLine( $"<img class='player-skill-image {statusClass}' src='https://wiki.guildwars2.com/images/7/74/Skill.png' alt='Unknown skill' title='{title}'>"); } else { writer.WriteLine( $"<img class='player-skill-image {statusClass}' src='{skillData.IconUrl}' alt='{encodedName}' title='{encodedName}'>"); } } else if (rotationItem is WeaponSwapItem weaponSwap) { writer.WriteLine( $"<img class='player-skill-image' src='https://wiki.guildwars2.com/images/c/ce/Weapon_Swap_Button.png' alt='Weapon Swap' title='Weapon Swap to ({weaponSwap.NewWeaponSet})'>"); writer.WriteLine($"<br>"); } } } else { writer.WriteLine("No rotation available."); } writer.WriteLine($@" </p> </div> </div> <div class='media-right'> <table class='table is-bordered'> <tr><th>Downs</th><td>{data.DownCount}</td></tr> <tr><th>Deaths</th><td>{data.DeathCount}</td></tr> </table> </div> </article> </div> "); } }
public override void WriteHtml(TextWriter writer) { writer.WriteLine("<div id='squad-rotation'></div>"); writer.WriteLine(@" <script> document.addEventListener('DOMContentLoaded', function() { var container = document.getElementById('squad-rotation'); var groups = new vis.DataSet([" ); int playerIndex = 0; foreach (var data in playerData) { writer.WriteLine($@"{{id: {playerIndex++}, content: '{data.Player.Name}'}},"); } writer.WriteLine(@" ]); var items = new vis.DataSet([" ); // TODO: Optimize for HTML size playerIndex = 0; foreach (var data in playerData) { foreach (var rotationItem in data.Rotation.Items) { if (rotationItem is SkillCastItem skillCast) { string className = skillCast.Type == SkillCastType.Cancel ? "className: 'cancel'" : skillCast.Type == SkillCastType.Reset ? "className: 'interrupt'" : ""; var htmlEncodedName = HttpUtility.HtmlEncode(skillCast.Skill.Name); var imageClass = skillCast.Type == SkillCastType.Cancel ? "cancel" : skillCast.Type == SkillCastType.Reset ? "interrupt" : ""; string imageSrc; string imageTitle; var skillData = gw2ApiData?.GetSkillData(skillCast.Skill); if (skillData == null) { if (skillCast.Skill.Id == SkillIds.ArcDpsDodge) { imageSrc = "https://wiki.guildwars2.com/images/c/cc/Dodge_Instructor.png"; imageTitle = "Dodge"; } else if (skillCast.Skill.Id == SkillIds.Revive) { imageSrc = "https://wiki.guildwars2.com/images/3/3d/Downed_ally.png"; imageTitle = "Revive"; } else { imageSrc = "https://wiki.guildwars2.com/images/7/74/Skill.png"; imageTitle = $"No API data for skill {htmlEncodedName} ({skillCast.Skill.Id})"; } } else { imageSrc = skillData.IconUrl; imageTitle = htmlEncodedName; } string image = $"<img class='rotation-skill-image {imageClass}' src='{imageSrc}' alt='{imageTitle}' title='{imageTitle}'>"; image = HttpUtility.JavaScriptStringEncode(image); writer.WriteLine( $"{{group: {playerIndex}, content: '{image}', start: {skillCast.ItemTime}, end: {skillCast.CastEndTime}, {className}}},"); } else if (rotationItem is WeaponSwapItem weaponSwap) { /* TODO: Looks ugly * string image = $"<img class='rotation-skill-image' src='https://wiki.guildwars2.com/images/c/ce/Weapon_Swap_Button.png' alt='Weapon Swap' title='Weapon Swap to ({weaponSwap.NewWeaponSet})'>"; * image = HttpUtility.JavaScriptStringEncode(image); * writer.WriteLine($"{{group: {playerIndex}, content: '{image}', start: {weaponSwap.ItemTime}, type: 'point'}},"); */ } else if (rotationItem is TemporaryStatusItem temporaryStatus) { string name = ""; string className = ""; switch (temporaryStatus.TemporaryStatus) { case TemporaryStatus.Downed: name = "Downed"; className = "downed"; break; case TemporaryStatus.ContinuumSplit: name = "Continuum Split"; className = "csplit"; break; default: name = "Unknown status"; break; } writer.WriteLine( $"{{group: {playerIndex}, content: '{name}', className: '{className}', type: 'background', start: {temporaryStatus.ItemTime}, end: {temporaryStatus.StatusEndTime}}},"); } } playerIndex++; } writer.WriteLine(@" ]); var options = { format: { minorLabels: function(date,scale,step) { return Math.floor(new Date(date).getTime() / 1000) + 's'; }, majorLabels: function(date,scale,step) { return Math.floor(new Date(date).getTime() / 1000) + 's'; }, }, stack: false, selectable: false, min: 0, //margin: {item: {horizontal: -1}}, //start: 0, // setting start results in all items in initial window being moved down end: 20000, orientation: 'top', }; var timeline = new vis.Timeline(container, items, groups, options); }); </script> "); foreach (var data in playerData) { var player = data.Player; } }