feat(character/achievements): add achievements page
This commit is contained in:
parent
ed424d63d0
commit
ab3b84f743
24 changed files with 2419 additions and 33 deletions
|
|
@ -71,6 +71,8 @@ export class Armory {
|
|||
await charsController.load();
|
||||
app.get("/character/:realm/:name", charsController.character.bind(charsController));
|
||||
app.get("/character/:realm/:name/talents", charsController.talents.bind(charsController));
|
||||
app.get("/character/:realm/:name/achievements", charsController.achievements.bind(charsController));
|
||||
app.get("/character/:realm/:character/achievements/data", charsController.achievementsData.bind(charsController));
|
||||
|
||||
this.gc();
|
||||
app.listen(listenPort, "0.0.0.0", () => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { RowDataPacket } from "mysql2/promise";
|
|||
|
||||
import { Armory } from "../Armory";
|
||||
import { IRealmConfig } from "../Config";
|
||||
import { IAchievement } from "../data/DbcReader";
|
||||
|
||||
interface ICharacterData {
|
||||
guid: number;
|
||||
|
|
@ -75,6 +76,7 @@ export class CharacterController {
|
|||
private itemSocketBonuses: { [key: number]: number };
|
||||
private mountSpells: number[];
|
||||
private mountBySpellId: { [key: number]: IMount };
|
||||
private achievementById: { [key: number]: IAchievement };
|
||||
|
||||
public constructor(armory: Armory) {
|
||||
this.armory = armory;
|
||||
|
|
@ -137,6 +139,11 @@ export class CharacterController {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.achievementById = {};
|
||||
for await (const achievement of this.armory.dbc.achievement()) {
|
||||
this.achievementById[achievement.id] = achievement;
|
||||
}
|
||||
}
|
||||
|
||||
public async character(req: express.Request, res: express.Response): Promise<void> {
|
||||
|
|
@ -214,10 +221,59 @@ export class CharacterController {
|
|||
});
|
||||
}
|
||||
|
||||
public async achievements(req: express.Request, res: express.Response): Promise<void> {
|
||||
const realmName = req.params.realm;
|
||||
const charName = req.params.name;
|
||||
|
||||
const realm = this.getRealm(realmName);
|
||||
if (realm === undefined) {
|
||||
// Could not find realm
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
const charData = await this.getCharacterData(realm.name, charName);
|
||||
if (charData === null) {
|
||||
// Could not find character
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
res.render("character-achievements.html", {
|
||||
title: `Armory - ${charName} - Achievements`,
|
||||
...this.makeSharedDataObject(realm, charData),
|
||||
});
|
||||
}
|
||||
|
||||
public async achievementsData(req: express.Request, res: express.Response): Promise<void> {
|
||||
const realmName = req.params.realm;
|
||||
const character = parseInt(req.params.character) || -1;
|
||||
|
||||
const realm = this.getRealm(realmName);
|
||||
if (realm === undefined) {
|
||||
// Could not find realm
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
const charData = await this.getCharacterData(realm.name, character);
|
||||
if (charData === null) {
|
||||
// Could not find character
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
categories: await this.armory.dbc.achievementCategory().toArray(),
|
||||
...await this.getAchievements(realm.name, charData),
|
||||
});
|
||||
}
|
||||
|
||||
private makeSharedDataObject(realm: IRealmConfig, charData: ICharacterData) {
|
||||
return {
|
||||
realm: realm.name,
|
||||
character: charData.name,
|
||||
name: charData.name,
|
||||
guid: charData.guid,
|
||||
race: RaceDisplayName[charData.race],
|
||||
class: ClassDisplayName[charData.class],
|
||||
level: charData.level,
|
||||
|
|
@ -229,12 +285,13 @@ export class CharacterController {
|
|||
return this.armory.config.realms.find(r => r.name.toLowerCase() === realm.toLowerCase());
|
||||
}
|
||||
|
||||
private async getCharacterData(realm: string, charName: string): Promise<ICharacterData> {
|
||||
private async getCharacterData(realm: string, character: string | number): Promise<ICharacterData> {
|
||||
const where = typeof character === "string" ? "LOWER(name) = LOWER(?)" : "guid = ?";
|
||||
const [rows, fields] = await this.armory.getCharactersDb(realm).query(`
|
||||
SELECT guid, name, race, class, gender, level, skin, face, hairStyle, hairColor, facialStyle, playerFlags, online
|
||||
FROM characters
|
||||
WHERE LOWER(name) = LOWER(?)
|
||||
`, [charName]);
|
||||
WHERE ${where}
|
||||
`, [character]);
|
||||
|
||||
if ((rows as RowDataPacket[]).length === 0) {
|
||||
return null;
|
||||
|
|
@ -655,4 +712,41 @@ export class CharacterController {
|
|||
|
||||
return glyphs;
|
||||
}
|
||||
|
||||
private async getAchievements(realm: string, charData: ICharacterData): Promise<{ achievements: any[]; earned: { [key: number]: any }; }> {
|
||||
const faction = [1, 3, 4, 7, 11].includes(charData.race) ? 1 : 0;
|
||||
|
||||
const promises = await this.armory.dbc.achievement()
|
||||
.filter(ach => ach.faction === -1 || ach.faction === faction)
|
||||
.map(async (ach) => {
|
||||
const icon = await this.armory.dbc.spellIcon().find(icon => icon.id === ach.iconId);
|
||||
return {
|
||||
id: ach.id,
|
||||
category: ach.category,
|
||||
title: ach.titleLang0,
|
||||
description: ach.descriptionLang0,
|
||||
points: ach.points,
|
||||
icon: this.processSpellIconTexture(icon?.textureFilename ?? ""),
|
||||
};
|
||||
})
|
||||
.toArray();
|
||||
const achievements = await Promise.all(promises);
|
||||
|
||||
const [rows, fields] = await this.armory.getCharactersDb(realm).query(`
|
||||
SELECT achievement, date
|
||||
FROM character_achievement
|
||||
WHERE guid = ?
|
||||
`, [charData.guid]);
|
||||
const earned = {};
|
||||
for (const row of rows as RowDataPacket[]) {
|
||||
earned[row.achievement] = {
|
||||
date: row.date,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
achievements,
|
||||
earned,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,23 @@ export interface IGlyphProperties {
|
|||
spellId: number;
|
||||
}
|
||||
|
||||
export interface IAchievement {
|
||||
id: number;
|
||||
faction: number;
|
||||
titleLang0: string;
|
||||
descriptionLang0: string;
|
||||
category: number;
|
||||
points: number;
|
||||
flags: number;
|
||||
iconId: number;
|
||||
}
|
||||
|
||||
export interface IAchievementCategory {
|
||||
id: number;
|
||||
parent: number;
|
||||
nameLang0: string;
|
||||
}
|
||||
|
||||
export interface IItemDbc {
|
||||
id: number;
|
||||
classId: number;
|
||||
|
|
@ -203,11 +220,7 @@ class DbcReader<T> {
|
|||
.map(header => camelCase(header).replace(/[\[\]]/g, ""));
|
||||
|
||||
for await (const arr of itr) {
|
||||
const cols = arr
|
||||
.map(value => {
|
||||
const parsed: number = parseInt(value, 10);
|
||||
return isNaN(parsed) ? value : parsed;
|
||||
});
|
||||
const cols = arr.map(value => isNaN(value as any) ? value : parseInt(value, 10));
|
||||
const row = {};
|
||||
headerCols.forEach((header, headerIdx) => {
|
||||
if (this.fields.length === 0 || this.fields.includes(header)) {
|
||||
|
|
@ -282,6 +295,8 @@ class DbcReader<T> {
|
|||
|
||||
const dir = path.join(process.cwd(), "data");
|
||||
export const DbcFiles = {
|
||||
achievement: path.join(dir, "Achievement_3.3.5_12340.csv"),
|
||||
achievementCategory: path.join(dir, "AchievementCategory_3.3.5_12340.csv"),
|
||||
glyphProperties: path.join(dir, "GlyphProperties_3.3.5_12340.csv"),
|
||||
item: path.join(dir, "Item_3.3.5_12340.csv"),
|
||||
itemRetail: path.join(dir, "Item_9.2.0_41462.csv"),
|
||||
|
|
@ -298,6 +313,8 @@ export const DbcFiles = {
|
|||
};
|
||||
|
||||
const dbcFields = {
|
||||
achievement: ["id", "faction", "titleLang0", "descriptionLang0", "category", "points", "flags", "iconId"],
|
||||
achievementCategory: ["id", "parent", "nameLang0"],
|
||||
glyphProperties: ["id", "spellId"],
|
||||
item: ["id", "classId", "displayInfoId", "inventoryType"],
|
||||
itemRetail: ["id", "inventoryType"],
|
||||
|
|
@ -314,6 +331,8 @@ const dbcFields = {
|
|||
};
|
||||
|
||||
export class DbcManager {
|
||||
private _achievement: IAchievement[];
|
||||
private _achievementCategory: IAchievementCategory[];
|
||||
private _glyphProperties: IGlyphProperties[];
|
||||
private _item: IItemDbc[];
|
||||
private _itemRetail: IItemRetailDbc[];
|
||||
|
|
@ -329,6 +348,8 @@ export class DbcManager {
|
|||
private _talentTab: ITalentTab[];
|
||||
|
||||
public async loadAllFiles(): Promise<void> {
|
||||
this._achievement = await this.read<IAchievement>(DbcFiles.achievement, dbcFields.achievement).toArray();
|
||||
this._achievementCategory = await this.read<IAchievementCategory>(DbcFiles.achievementCategory, dbcFields.achievementCategory).toArray();
|
||||
this._glyphProperties = await this.read<IGlyphProperties>(DbcFiles.glyphProperties, dbcFields.glyphProperties).toArray();
|
||||
this._item = await this.read<IItemDbc>(DbcFiles.item, dbcFields.item).toArray();
|
||||
this._itemRetail = await this.read<IItemRetailDbc>(DbcFiles.itemRetail, dbcFields.itemRetail).toArray();
|
||||
|
|
@ -344,6 +365,14 @@ export class DbcManager {
|
|||
this._talentTab = await this.read<ITalentTab>(DbcFiles.talentTab, dbcFields.talentTab).toArray();
|
||||
}
|
||||
|
||||
public achievement() {
|
||||
return this.getLoadedDataOrRead(DbcFiles.achievement, this._achievement, dbcFields.achievement);
|
||||
}
|
||||
|
||||
public achievementCategory() {
|
||||
return this.getLoadedDataOrRead(DbcFiles.achievementCategory, this._achievementCategory, dbcFields.achievementCategory);
|
||||
}
|
||||
|
||||
public glyphProperties() {
|
||||
return this.getLoadedDataOrRead(DbcFiles.glyphProperties, this._glyphProperties, dbcFields.glyphProperties);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue