diff --git a/README.md b/README.md index 006aee9..ad246c7 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,8 @@ This repository is used in production over at [ChromieCraft](https://www.chromie - [X] Multiple realms support - [ ] PvE ladder - [ ] PvP ladder +- [X] Arena ladder +- [ ] Achievements ladder See the [open issues](https://github.com/r-o-b-o-t-o/azerothcore-armory/issues) for a list of suggested features and known issues. diff --git a/src/armory/Armory.ts b/src/armory/Armory.ts index 5ee61c8..b2c621a 100644 --- a/src/armory/Armory.ts +++ b/src/armory/Armory.ts @@ -13,6 +13,7 @@ import { CharacterCustomization } from "./data/CharacterCustomization"; import { IndexController } from "./controllers/IndexController"; import { CharacterController } from "./controllers/CharacterController"; import { GuildController } from "./controllers/GuildController"; +import { ArenaController } from "./controllers/ArenaController"; export class Armory { public characterCustomization: CharacterCustomization; @@ -157,6 +158,11 @@ export class Armory { app.get("/guild/:realm/:name", this.wrapRoute(guildsController.guild.bind(guildsController))); app.get("/guild/:realm/:guild/members", this.wrapRoute(guildsController.members.bind(guildsController))); + const arenaController = new ArenaController(this); + app.get("/arena", this.wrapRoute(arenaController.index.bind(arenaController))); + app.get("/arena/ladder", this.wrapRoute(arenaController.ladder.bind(arenaController))); + app.get("/arena/team/:realm/:name", this.wrapRoute(arenaController.team.bind(arenaController))); + app.use((err, req: express.Request, res: express.Response, next: express.NextFunction) => { // Error handler diff --git a/src/armory/controllers/ArenaController.ts b/src/armory/controllers/ArenaController.ts new file mode 100644 index 0000000..4ddbfec --- /dev/null +++ b/src/armory/controllers/ArenaController.ts @@ -0,0 +1,156 @@ +import * as express from "express"; +import { RowDataPacket } from "mysql2"; + +import { Armory } from "../Armory"; +import { IRealmConfig } from "../Config"; +import { IEmblem, Utils } from "../Utils"; +import { DataTablesSsp } from "../DataTablesSsp"; + +interface ITeamMemberData { + name: string; + weekGames: number; + weekWins: number; + seasonGames: number; + seasonWins: number; + personalRating: number; + race: number; + class: number; + gender: string; + online: boolean; +} + +interface ITeamData { + name: string; + captainGuid: number; + type: number; + rating: number; + seasonGames: number; + seasonWins: number; + weekGames: number; + weekWins: number; + emblem: IEmblem; + members: ITeamMemberData[]; +} + +export class ArenaController { + private armory: Armory; + + public constructor(armory: Armory) { + this.armory = armory; + } + + public async index(req: express.Request, res: express.Response, next: express.NextFunction): Promise { + res.render("ladder-arena.hbs", { + title: `Arena Ladder`, + realms: this.armory.config.realms.map((r) => r.name), + }); + } + + public async team(req: express.Request, res: express.Response, next: express.NextFunction): Promise { + const realmName = req.params.realm; + const teamName = req.params.name; + + const realm = this.armory.getRealm(realmName); + if (realm === undefined) { + // Could not find realm + return next(404); + } + + const teamData = await this.getTeamData(realm, teamName); + if (teamData === null) { + // Could not find guild + return next(404); + } + + res.render("arena-team.hbs", { + title: `Armory - ${teamData.name}`, + realm: realm.name, + ...teamData, + }); + } + + public async ladder(req: express.Request, res: express.Response, next: express.NextFunction): Promise { + const realmName = req.query.realm as string; + const realm = realmName === undefined ? this.armory.config.realms[0] : this.armory.config.realms.find((r) => r.name === realmName); + if (realm === undefined) { + return next(400); + } + + const teamSize = parseInt(req.query.teamsize as string); + if (!(teamSize === 2 || teamSize === 3 || teamSize === 5)) { + return next(400); + } + + const db = this.armory.getCharactersDb(realm.name); + const charSet = await this.armory.getDatabaseCharset(realm.name); + + const ssp = new DataTablesSsp(req.query, db, "arena_team", "arenaTeamId", [ + { name: "name", collation: `${charSet}_general_ci` }, + { name: "rating" }, + { name: "seasonWins" }, + { name: "seasonGames" }, + ]); + + const result = await ssp.where("`type` = " + teamSize).run(this.armory.config.dbQueryTimeout); + + res.json({ + ...result, + realm: realm.name, + teamSize, + }); + } + + private async getTeamData(realm: IRealmConfig, teamName: string): Promise { + const db = this.armory.getCharactersDb(realm.name); + const [rows] = await db.query({ + sql: ` + SELECT arenaTeamId, name, captainGuid, type, rating, seasonGames, seasonWins, weekGames, weekWins, emblemStyle, emblemColor, borderStyle, borderColor, backgroundColor AS background + FROM arena_team WHERE name = ? + `, + values: [teamName], + timeout: this.armory.config.dbQueryTimeout, + }); + if ((rows as RowDataPacket[]).length === 0) { + return null; + } + const team = rows[0]; + + const [memberRows] = await db.query({ + sql: ` + SELECT name, weekGames, weekWins, seasonGames, seasonWins, personalRating, race, class, gender, online + FROM arena_team_member + LEFT JOIN characters ON arena_team_member.guid = characters.guid + WHERE arenaTeamId = ? + `, + values: [team.arenaTeamId], + timeout: this.armory.config.dbQueryTimeout, + }); + const members: ITeamMemberData[] = (memberRows as RowDataPacket[]).map((row) => { + return { + name: row.name, + weekGames: row.weekGames, + weekWins: row.weekWins, + seasonGames: row.seasonGames, + seasonWins: row.seasonWins, + personalRating: row.personalRating, + race: Utils.raceNames[row.race], + class: Utils.classNames[row.class], + gender: row.gender === 0 ? "male" : "female", + online: row.online === 1, + }; + }); + + return { + name: team.name, + captainGuid: team.captainGuid, + type: team.type, + rating: team.rating, + seasonGames: team.seasonGames, + seasonWins: team.seasonWins, + weekGames: team.weekGames, + weekWins: team.weekWins, + emblem: Utils.makeEmblemObject(team, false), + members, + }; + } +} diff --git a/src/armory/controllers/CharacterController.ts b/src/armory/controllers/CharacterController.ts index 4eabe38..6452112 100644 --- a/src/armory/controllers/CharacterController.ts +++ b/src/armory/controllers/CharacterController.ts @@ -319,6 +319,7 @@ export class CharacterController { res.render("character-pvp.hbs", { title: `Armory - ${charData.name} - PvP`, + realm: realm.name, ...this.makeSharedDataObject(realm, charData), faction: Utils.getFactionFromRaceId(charData.race), kills: await this.getPvpKills(realm.name, charData.guid), diff --git a/static/arena-team.hbs b/static/arena-team.hbs new file mode 100644 index 0000000..350c8f5 --- /dev/null +++ b/static/arena-team.hbs @@ -0,0 +1,115 @@ + +{{> datatables}} +{{> emblems}} + +Back to Arena Ladder +

+ +
+
+
+ +
+
+ +
+
{{name}}
+
+ {{type}}v{{type}} Team, {{realm}} +
+
+
+ +
+ +
Rating: {{rating}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
GamesWinsLossesWin Rate
Week{{weekGames}}{{weekWins}}{{subtract weekGames weekWins}} + {{#if (isnt weekGames 0)}} + {{toPrecision (multiply (divide weekWins weekGames) 100) 3}} % + {{/if}} +
Season{{seasonGames}}{{seasonWins}}{{subtract seasonGames seasonWins}} + {{#if (isnt seasonGames 0)}} + {{toPrecision (multiply (divide seasonWins seasonGames) 100) 3}} % + {{/if}} +
+ +
+
Members:
+ + + + + + + + + + + + + + + + {{#each members}} + + + + + + + + + + + + {{/each}} + +
NameClassRaceWeek WinsWeek LossesSeason WinsSeason LossesRatingOnline
+ + {{name}} + + + + + + {{weekWins}}{{subtract weekGames weekWins}}{{seasonWins}}{{subtract seasonGames seasonWins}}{{personalRating}}{{#if online}}🟢{{else}}🔴{{/if}}
+ + diff --git a/static/character-pvp.hbs b/static/character-pvp.hbs index 063be3e..17be745 100644 --- a/static/character-pvp.hbs +++ b/static/character-pvp.hbs @@ -20,7 +20,9 @@
-
{{this.name}}
+
{{this.type}}v{{this.type}} Team
Rating: {{this.rating}}
{{this.seasonWins}} W / {{subtract this.seasonGames this.seasonWins}} L
diff --git a/static/css/arena-team.css b/static/css/arena-team.css new file mode 100644 index 0000000..5fc7071 --- /dev/null +++ b/static/css/arena-team.css @@ -0,0 +1,29 @@ +#arena-header { + display: flex; +} + +#arena-header .emblem-container { + margin-right: 1em; +} + +#arena-header .info { + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; +} + +#arena-header .info .team-name { + margin-top: -8px; +} + +#stats td, +#stats th { + border: 1px solid #dddddd; + text-align: left; + padding: 8px; +} + +.dataTables_info { + display: none; +} diff --git a/static/css/index.css b/static/css/index.css index dbe53d4..bdf4160 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -1,7 +1,3 @@ -#select-realm { - margin-bottom: 16px; -} - #results_processing { height: 70px; } diff --git a/static/css/ladder-arena.css b/static/css/ladder-arena.css new file mode 100644 index 0000000..bd81c04 --- /dev/null +++ b/static/css/ladder-arena.css @@ -0,0 +1,8 @@ +#select-arena-type-container { + display: flex; +} + +#select-arena-type-container .arena-type-label { + align-self: center; + margin-right: 4px; +} diff --git a/static/index.hbs b/static/index.hbs index 7e89a0f..19d3e95 100644 --- a/static/index.hbs +++ b/static/index.hbs @@ -3,8 +3,11 @@

Armory

-{{#if (not (equalsLength realms 1))}} +Arena Ladder  +

+ +{{#if (not (equalsLength realms 1))}}
Realm:
diff --git a/static/ladder-arena.hbs b/static/ladder-arena.hbs new file mode 100644 index 0000000..20a18f7 --- /dev/null +++ b/static/ladder-arena.hbs @@ -0,0 +1,101 @@ + + +{{> datatables}} + +

Arena Ladder

+ +Armory  + +

+ +{{#if (not (equalsLength realms 1))}} +
+ Realm: +
+ +
+   +
+ +
+
+{{else}} +
+ Type: +
+ +
+
+{{/if}} + +
+ + + + + + + + + + + +
NameRatingWinsLosses
+ + diff --git a/static/partials/character-header.hbs b/static/partials/character-header.hbs index 950913a..1d013b3 100644 --- a/static/partials/character-header.hbs +++ b/static/partials/character-header.hbs @@ -1,10 +1,10 @@ Back to Armory

-Character  -Talents  -Achievements -PvP +Character  +Talents  +Achievements  +PvP