Skip to content

Commit

Permalink
Add match stats to the series output (#9)
Browse files Browse the repository at this point in the history
Co-authored-by: Matthew Lee <[email protected]>
  • Loading branch information
davidhouweling and MatthewLeeCode authored Oct 22, 2024
1 parent 5819f46 commit b38b0d0
Show file tree
Hide file tree
Showing 25 changed files with 565 additions and 47 deletions.
File renamed without changes.
8 changes: 8 additions & 0 deletions src/commands/stats/embeds/attrition-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed } from "./base-match-embed.mjs";

export class AttritionMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerAttrition> {
override getPlayerObjectiveStats(): Map<string, string> {
return new Map([]);
}
}
106 changes: 106 additions & 0 deletions src/commands/stats/embeds/base-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { GameVariantCategory, MatchStats } from "halo-infinite-api";
import { HaloService } from "../../../services/halo/halo.mjs";
import { EmbedBuilder } from "discord.js";
import { Preconditions } from "../../../base/preconditions.mjs";

export type PlayerStats<TCategory extends GameVariantCategory> =
MatchStats<TCategory>["Players"][0]["PlayerTeamStats"][0]["Stats"];

export abstract class BaseMatchEmbed<TCategory extends GameVariantCategory> {
constructor(protected readonly haloService: HaloService) {}

protected abstract getPlayerObjectiveStats(stats: PlayerStats<TCategory>): Map<string, string>;

protected getPlayerSlayerStats(stats: PlayerStats<TCategory>): Map<string, string> {
const { CoreStats } = stats;
return new Map([
["Kills", CoreStats.Kills.toString()],
["Deaths", CoreStats.Deaths.toString()],
["Assists", CoreStats.Assists.toString()],
["KDA", CoreStats.KDA.toString()],
["Headshot kills", CoreStats.HeadshotKills.toString()],
["Shots H:F", `${CoreStats.ShotsHit.toString()}:${CoreStats.ShotsFired.toString()}`],
["Accuracy", `${CoreStats.Accuracy.toString()}%`],
["Damage D:T", `${CoreStats.DamageDealt.toString()}:${CoreStats.DamageTaken.toString()}`],
["Av life duration", this.haloService.getReadableDuration(CoreStats.AverageLifeDuration)],
["Av damage/life", (CoreStats.DamageDealt / CoreStats.Deaths).toFixed(2)],
]);
}

async getEmbed(match: MatchStats, players: Map<string, string>) {
const gameTypeAndMap = await this.haloService.getGameTypeAndMap(match);

const embed = new EmbedBuilder()
.setTitle(gameTypeAndMap)
.setURL(`https://halodatahive.com/Infinite/Match/${match.MatchId}`);

for (const team of match.Teams) {
embed.addFields({
name: this.haloService.getTeamName(team.TeamId),
value: `Team Score: ${team.Stats.CoreStats.Score.toString()}`,
inline: false,
});

const teamPlayers = match.Players.filter((player) =>
player.PlayerTeamStats.find((teamStats) => teamStats.TeamId === team.TeamId),
).sort((a, b) => {
if (a.Rank - b.Rank !== 0) {
return a.Rank - b.Rank;
}

const aStats = Preconditions.checkExists(
a.PlayerTeamStats.find((teamStats) => teamStats.TeamId === team.TeamId),
);
const bStats = Preconditions.checkExists(
b.PlayerTeamStats.find((teamStats) => teamStats.TeamId === team.TeamId),
);
return aStats.Stats.CoreStats.Score - bStats.Stats.CoreStats.Score;
});

let playerFields = [];
for (const teamPlayer of teamPlayers) {
const playerXuid = this.haloService.getPlayerXuid(teamPlayer);
const playerGamertag = Preconditions.checkExists(players.get(playerXuid));
const playerStats = Preconditions.checkExists(
teamPlayer.PlayerTeamStats.find((teamStats) => teamStats.TeamId === team.TeamId),
"Unable to match player to team",
) as MatchStats<TCategory>["Players"][0]["PlayerTeamStats"][0];

const {
Stats: { CoreStats: coreStats },
} = playerStats;

const outputStats = [
`Rank: ${teamPlayer.Rank.toString()}`,
`Score: ${coreStats.Score.toString()}`,
...this.playerStatsToFields(this.getPlayerSlayerStats(playerStats.Stats)),
...this.playerStatsToFields(this.getPlayerObjectiveStats(playerStats.Stats)),
];
playerFields.push({
name: playerGamertag,
value: `\`\`\`${outputStats.join("\n")}\`\`\``,
inline: true,
});

// If two players are added, or if it's the last player, push to embed and reset
if (playerFields.length === 2 || teamPlayer === teamPlayers[teamPlayers.length - 1]) {
embed.addFields(playerFields);
playerFields = [];

// Adds a new row
embed.addFields({
name: "\n",
value: "\n",
inline: false,
});
}
}
}

return embed;
}

private playerStatsToFields(playerStats: Map<string, string>): string[] {
return Array.from(playerStats.entries()).map(([key, value]) => `${key}: ${value}`);
}
}
15 changes: 15 additions & 0 deletions src/commands/stats/embeds/ctf-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed, PlayerStats } from "./base-match-embed.mjs";

export class CtfMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerCtf> {
override getPlayerObjectiveStats(stats: PlayerStats<GameVariantCategory.MultiplayerCtf>): Map<string, string> {
return new Map([
["Captures", stats.CaptureTheFlagStats.FlagCaptures.toString()],
["Captures assists", stats.CaptureTheFlagStats.FlagCaptureAssists.toString()],
["Carrier time", this.haloService.getReadableDuration(stats.CaptureTheFlagStats.TimeAsFlagCarrier)],
["Grabs", stats.CaptureTheFlagStats.FlagGrabs.toString()],
["Returns", stats.CaptureTheFlagStats.FlagReturns.toString()],
["Carriers killed", stats.CaptureTheFlagStats.FlagCarriersKilled.toString()],
]);
}
}
17 changes: 17 additions & 0 deletions src/commands/stats/embeds/elimination-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed, PlayerStats } from "./base-match-embed.mjs";

export class EliminationMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerElimination> {
override getPlayerObjectiveStats(
stats: PlayerStats<GameVariantCategory.MultiplayerElimination>,
): Map<string, string> {
return new Map([
["Eliminations", stats.EliminationStats.Eliminations.toString()],
["Elimination assists", stats.EliminationStats.EliminationAssists.toString()],
["Allies revived", stats.EliminationStats.AlliesRevived.toString()],
["Rounds Survived", stats.EliminationStats.RoundsSurvived.toString()],
["Times revived by ally", stats.EliminationStats.TimesRevivedByAlly.toString()],
["Enemy revives denied", stats.EliminationStats.EnemyRevivesDenied.toString()],
]);
}
}
8 changes: 8 additions & 0 deletions src/commands/stats/embeds/escalation-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed } from "./base-match-embed.mjs";

export class EscalationMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerEscalation> {
override getPlayerObjectiveStats(): Map<string, string> {
return new Map();
}
}
14 changes: 14 additions & 0 deletions src/commands/stats/embeds/extraction-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed, PlayerStats } from "./base-match-embed.mjs";

export class ExtractionMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerExtraction> {
override getPlayerObjectiveStats(stats: PlayerStats<GameVariantCategory.MultiplayerExtraction>): Map<string, string> {
return new Map([
["Successful extractions", stats.ExtractionStats.SuccessfulExtractions.toString()],
["Extraction initiations completed", stats.ExtractionStats.ExtractionInitiationsCompleted.toString()],
["Extraction initiations denied", stats.ExtractionStats.ExtractionInitiationsDenied.toString()],
["Extraction conversions completed", stats.ExtractionStats.ExtractionConversionsCompleted.toString()],
["Extraction conversions denied", stats.ExtractionStats.ExtractionConversionsDenied.toString()],
]);
}
}
8 changes: 8 additions & 0 deletions src/commands/stats/embeds/fiesta-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed } from "./base-match-embed.mjs";

export class FiestaMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerFiesta> {
override getPlayerObjectiveStats(): Map<string, string> {
return new Map([]);
}
}
23 changes: 23 additions & 0 deletions src/commands/stats/embeds/firefight-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed, PlayerStats } from "./base-match-embed.mjs";

export class FirefightMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerFirefight> {
override getPlayerObjectiveStats(stats: PlayerStats<GameVariantCategory.MultiplayerFirefight>): Map<string, string> {
return new Map([
["Eliminations", stats.EliminationStats.Eliminations.toString()],
["Elimination assists", stats.EliminationStats.EliminationAssists.toString()],
["Allies revived", stats.EliminationStats.AlliesRevived.toString()],
["Rounds Survived", stats.EliminationStats.RoundsSurvived.toString()],
["Times revived by ally", stats.EliminationStats.TimesRevivedByAlly.toString()],
["Enemy revives denied", stats.EliminationStats.EnemyRevivesDenied.toString()],
["Boss kills", stats.PveStats.BossKills.toString()],
["Hunter kills", stats.PveStats.HunterKills.toString()],
["Elite kills", stats.PveStats.EliteKills.toString()],
["Jackal kills", stats.PveStats.JackalKills.toString()],
["Grunt kills", stats.PveStats.GruntKills.toString()],
["Brute kills", stats.PveStats.BruteKills.toString()],
["Sentinel kills", stats.PveStats.SentinelKills.toString()],
["Skimmer kills", stats.PveStats.SkimmerKills.toString()],
]);
}
}
8 changes: 8 additions & 0 deletions src/commands/stats/embeds/grifball-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed } from "./base-match-embed.mjs";

export class GrifballMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerGrifball> {
override getPlayerObjectiveStats(): Map<string, string> {
return new Map([]);
}
}
19 changes: 19 additions & 0 deletions src/commands/stats/embeds/infection-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed, PlayerStats } from "./base-match-embed.mjs";

export class InfectionMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerInfection> {
override getPlayerObjectiveStats(stats: PlayerStats<GameVariantCategory.MultiplayerInfection>): Map<string, string> {
return new Map([
["Alphas killed", stats.InfectionSTats.AlphasKilled.toString()],
["Infected killed", stats.InfectionSTats.InfectedKilled.toString()],
["Kills as last spartan standing", stats.InfectionSTats.KillsAsLastSpartanStanding.toString()],
["Rounds survived as spartan", stats.InfectionSTats.RoundsSurvivedAsSpartan.toString()],
[
"Time as last spartan standing",
this.haloService.getReadableDuration(stats.InfectionSTats.TimeAsLastSpartanStanding),
],
["Spartans infected", stats.InfectionSTats.SpartansInfected.toString()],
["Spartans infected as alpha", stats.InfectionSTats.SpartansInfectedAsAlpha.toString()],
]);
}
}
16 changes: 16 additions & 0 deletions src/commands/stats/embeds/koth-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed, PlayerStats } from "./base-match-embed.mjs";

export class KOTHMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerKingOfTheHill> {
override getPlayerObjectiveStats(
stats: PlayerStats<GameVariantCategory.MultiplayerKingOfTheHill>,
): Map<string, string> {
return new Map([
["Captures", stats.ZonesStats.StrongholdCaptures.toString()],
["Occupation time", this.haloService.getReadableDuration(stats.ZonesStats.StrongholdOccupationTime)],
["Secures", stats.ZonesStats.StrongholdSecures.toString()],
["Offensive kills", stats.ZonesStats.StrongholdOffensiveKills.toString()],
["Defensive kills", stats.ZonesStats.StrongholdDefensiveKills.toString()],
]);
}
}
8 changes: 8 additions & 0 deletions src/commands/stats/embeds/land-grab-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed } from "./base-match-embed.mjs";

export class LandGrabMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerLandGrab> {
override getPlayerObjectiveStats(): Map<string, string> {
return new Map([]);
}
}
8 changes: 8 additions & 0 deletions src/commands/stats/embeds/minigame-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed } from "./base-match-embed.mjs";

export class MinigameMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerMinigame> {
override getPlayerObjectiveStats(): Map<string, string> {
return new Map([]);
}
}
11 changes: 11 additions & 0 deletions src/commands/stats/embeds/oddball-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed, PlayerStats } from "./base-match-embed.mjs";

export class OddballMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerOddball> {
override getPlayerObjectiveStats(stats: PlayerStats<GameVariantCategory.MultiplayerOddball>): Map<string, string> {
return new Map([
["Total time as carrier", this.haloService.getReadableDuration(stats.OddballStats.TimeAsSkullCarrier)],
["Longest time as carrier", this.haloService.getReadableDuration(stats.OddballStats.LongestTimeAsSkullCarrier)],
]);
}
}
8 changes: 8 additions & 0 deletions src/commands/stats/embeds/slayer-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed } from "./base-match-embed.mjs";

export class SlayerMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerSlayer> {
override getPlayerObjectiveStats(): Map<string, string> {
return new Map([]);
}
}
15 changes: 15 additions & 0 deletions src/commands/stats/embeds/stockpile-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed, PlayerStats } from "./base-match-embed.mjs";

export class StockpileMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerStockpile> {
override getPlayerObjectiveStats(stats: PlayerStats<GameVariantCategory.MultiplayerStockpile>): Map<string, string> {
return new Map([
["Power seeds deposited", stats.StockpileStats.PowerSeedsDeposited.toString()],
["Power seeds stolen", stats.StockpileStats.PowerSeedsStolen.toString()],
["Kills as power seed carrier", stats.StockpileStats.KillsAsPowerSeedCarrier.toString()],
["Power seed carriers killed", stats.StockpileStats.PowerSeedCarriersKilled.toString()],
["Time as power seed carrier", this.haloService.getReadableDuration(stats.StockpileStats.TimeAsPowerSeedCarrier)],
["Time as power seed driver", this.haloService.getReadableDuration(stats.StockpileStats.TimeAsPowerSeedDriver)],
]);
}
}
16 changes: 16 additions & 0 deletions src/commands/stats/embeds/strongholds-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed, PlayerStats } from "./base-match-embed.mjs";

export class StrongholdsMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerStrongholds> {
override getPlayerObjectiveStats(
stats: PlayerStats<GameVariantCategory.MultiplayerStrongholds>,
): Map<string, string> {
return new Map([
["Captures", stats.ZonesStats.StrongholdCaptures.toString()],
["Occupation time", this.haloService.getReadableDuration(stats.ZonesStats.StrongholdOccupationTime)],
["Secures", stats.ZonesStats.StrongholdSecures.toString()],
["Offensive kills", stats.ZonesStats.StrongholdOffensiveKills.toString()],
["Defensive kills", stats.ZonesStats.StrongholdDefensiveKills.toString()],
]);
}
}
8 changes: 8 additions & 0 deletions src/commands/stats/embeds/total-control-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed } from "./base-match-embed.mjs";

export class TotalControlMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerTotalControl> {
override getPlayerObjectiveStats(): Map<string, string> {
return new Map([]);
}
}
8 changes: 8 additions & 0 deletions src/commands/stats/embeds/unknown-match-embed.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GameVariantCategory } from "halo-infinite-api";
import { BaseMatchEmbed } from "./base-match-embed.mjs";

export class UnknownMatchEmbed extends BaseMatchEmbed<GameVariantCategory.MultiplayerTotalControl> {
override getPlayerObjectiveStats(): Map<string, string> {
return new Map([]);
}
}
Loading

0 comments on commit b38b0d0

Please sign in to comment.