add quests page
This commit is contained in:
parent
9a556bacdd
commit
91257a09f4
7 changed files with 2711 additions and 1 deletions
|
|
@ -155,6 +155,7 @@ export class Armory {
|
|||
app.get("/character/:realm/:character/achievements/data", this.wrapRoute(charsController.achievementsData.bind(charsController)));
|
||||
app.get("/character/:realm/:name/pvp", this.wrapRoute(charsController.pvp.bind(charsController)));
|
||||
app.get("/character/:realm/:name/reputation", this.wrapRoute(charsController.reputation.bind(charsController)));
|
||||
app.get("/character/:realm/:name/quests", this.wrapRoute(charsController.quests.bind(charsController)));
|
||||
|
||||
const guildsController = new GuildController(this);
|
||||
app.get("/guild/:realm/:name", this.wrapRoute(guildsController.guild.bind(guildsController)));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { RowDataPacket } from "mysql2/promise";
|
|||
import { Armory } from "../Armory";
|
||||
import { IRealmConfig } from "../Config";
|
||||
import { IEmblem, Utils } from "../Utils";
|
||||
import { IAchievement as IAchievementDbc, ISkillDbc } from "../data/DbcReader";
|
||||
import { IAchievement as IAchievementDbc, ISkillDbc, IAreas } from "../data/DbcReader";
|
||||
|
||||
interface ICharacterData {
|
||||
guid: number;
|
||||
|
|
@ -90,6 +90,15 @@ interface IReputation {
|
|||
expansionId: number;
|
||||
}
|
||||
|
||||
interface IQuest {
|
||||
id: number;
|
||||
title: string;
|
||||
status: 'Completed' | 'In Progress';
|
||||
level: number;
|
||||
questLevel: number;
|
||||
questSortID: number;
|
||||
}
|
||||
|
||||
const ItemClassGem = 3;
|
||||
const SpellMechanicMounted = 21;
|
||||
const RaceDisplayName = {
|
||||
|
|
@ -119,6 +128,7 @@ const ClassDisplayName = {
|
|||
|
||||
export class CharacterController {
|
||||
private armory: Armory;
|
||||
private areaById: { [key: number]: IAreas };
|
||||
private itemInventoryTypes: { [key: number]: number };
|
||||
private itemIcons: { [key: number]: number };
|
||||
private gemItems: { [key: number]: boolean };
|
||||
|
|
@ -204,6 +214,10 @@ export class CharacterController {
|
|||
for await (const skill of this.armory.dbc.skill()) {
|
||||
this.skillById[skill.id] = skill;
|
||||
}
|
||||
this.areaById = {};
|
||||
for await (const area of this.armory.dbc.areas()) {
|
||||
this.areaById[area.id] = area;
|
||||
}
|
||||
}
|
||||
|
||||
public async character(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||
|
|
@ -349,6 +363,44 @@ export class CharacterController {
|
|||
});
|
||||
}
|
||||
|
||||
public async quests(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||
const realmName = req.params.realm;
|
||||
const charName = req.params.name;
|
||||
|
||||
const realm = this.armory.getRealm(realmName);
|
||||
if (realm === undefined) {
|
||||
return next(404);
|
||||
}
|
||||
|
||||
const charData = await this.getCharacterData(realm, charName);
|
||||
if (charData === null) {
|
||||
return next(404);
|
||||
}
|
||||
|
||||
const quests = await this.getQuests(realm.name, charData.guid);
|
||||
|
||||
// Group quests by zone/profession
|
||||
const questsByCategory = quests.reduce((acc, quest) => {
|
||||
const category = quest.questSortID > 0 ?
|
||||
this.getZoneName(quest.questSortID):
|
||||
this.getProfessionName(quest.questSortID);
|
||||
|
||||
if (!acc[category]) {
|
||||
acc[category] = [];
|
||||
}
|
||||
acc[category].push(quest);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
res.render("character-quests.hbs", {
|
||||
title: `Armory - ${charData.name} - Quests`,
|
||||
...this.makeSharedDataObject(realm, charData),
|
||||
data: {
|
||||
categories: questsByCategory
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async getReputations(realm: string, character: number): Promise<IReputation[]> {
|
||||
const [rows] = await this.armory.getCharactersDb(realm).query({
|
||||
sql: `
|
||||
|
|
@ -423,6 +475,145 @@ export class CharacterController {
|
|||
return 2;
|
||||
}
|
||||
|
||||
private async getQuests(realm: string, character: number): Promise<IQuest[]> {
|
||||
// Get completed and rewarded quests
|
||||
const [completedRows] = await this.armory.getCharactersDb(realm).query({
|
||||
sql: `
|
||||
SELECT quest FROM (
|
||||
SELECT quest FROM character_queststatus
|
||||
WHERE guid = ? AND status = 1
|
||||
UNION
|
||||
SELECT quest FROM character_queststatus_rewarded
|
||||
WHERE guid = ?
|
||||
) AS completed_quests
|
||||
`,
|
||||
values: [character, character],
|
||||
timeout: this.armory.config.dbQueryTimeout,
|
||||
});
|
||||
|
||||
// Get in progress quests
|
||||
const [inProgressRows] = await this.armory.getCharactersDb(realm).query({
|
||||
sql: `
|
||||
SELECT quest
|
||||
FROM character_queststatus
|
||||
WHERE guid = ? AND status = 3
|
||||
`,
|
||||
values: [character],
|
||||
timeout: this.armory.config.dbQueryTimeout,
|
||||
});
|
||||
|
||||
const quests: IQuest[] = [];
|
||||
|
||||
// Process completed quests
|
||||
for (const row of completedRows as RowDataPacket[]) {
|
||||
const questInfo = await this.getQuestInfo(row.quest);
|
||||
if (questInfo) {
|
||||
quests.push({
|
||||
id: row.quest,
|
||||
title: questInfo.title,
|
||||
status: 'Completed',
|
||||
level: questInfo.minLevel,
|
||||
questLevel: questInfo.questLevel,
|
||||
questSortID: questInfo.questSortID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Process in progress quests
|
||||
for (const row of inProgressRows as RowDataPacket[]) {
|
||||
const questInfo = await this.getQuestInfo(row.quest);
|
||||
if (questInfo) {
|
||||
quests.push({
|
||||
id: row.quest,
|
||||
title: questInfo.title,
|
||||
status: 'In Progress',
|
||||
level: questInfo.minLevel,
|
||||
questLevel: questInfo.questLevel,
|
||||
questSortID: questInfo.questSortID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return quests;
|
||||
}
|
||||
|
||||
private async getQuestInfo(questId: number): Promise<any> {
|
||||
const [rows] = await this.armory.worldDb.query({
|
||||
sql: `
|
||||
SELECT ID, LogTitle as title, MinLevel as minLevel, QuestLevel as questLevel, QuestSortID as questSortID
|
||||
FROM quest_template
|
||||
WHERE ID = ?
|
||||
`,
|
||||
values: [questId],
|
||||
timeout: this.armory.config.dbQueryTimeout,
|
||||
});
|
||||
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
private getZoneName(zoneId: number): string {
|
||||
this.areaById[zoneId]?.zoneName
|
||||
return this.areaById[zoneId]?.zoneName || `Zone ${zoneId}`;
|
||||
}
|
||||
|
||||
private getProfessionName(professionId: number): string {
|
||||
const questTypes = {
|
||||
// Negative IDs (Classes and Professions)
|
||||
// Classes
|
||||
"-61": "Warlock",
|
||||
"-81": "Warrior",
|
||||
"-82": "Shaman",
|
||||
"-141": "Paladin",
|
||||
"-161": "Mage",
|
||||
"-162": "Rogue",
|
||||
"-261": "Hunter",
|
||||
"-262": "Priest",
|
||||
"-263": "Druid",
|
||||
"-372": "Death Knight",
|
||||
// Professions
|
||||
"-24": "Herbalism",
|
||||
"-101": "Fishing",
|
||||
"-121": "Blacksmithing",
|
||||
"-181": "Alchemy",
|
||||
"-182": "Leatherworking",
|
||||
"-201": "Engineering",
|
||||
"-264": "Tailoring",
|
||||
"-304": "Cooking",
|
||||
"-324": "First Aid",
|
||||
"-371": "Inscription",
|
||||
"-373": "Jewelcrafting",
|
||||
"-762": "Riding",
|
||||
// Misc
|
||||
"-1": "Epic",
|
||||
"-21": "Wailing Caverns",
|
||||
"-22": "Seasonal",
|
||||
"-23": "Undercity",
|
||||
"-25": "Battlegrounds",
|
||||
"-41": "Uldaman",
|
||||
"-221": "Treasure Map",
|
||||
"-241": "Tournament",
|
||||
"-284": "Special",
|
||||
"-344": "Legendary",
|
||||
"-364": "Darkmoon Faire",
|
||||
"-365": "Ahn'Qiraj War",
|
||||
"-366": "Lunar Festival",
|
||||
"-367": "Reputation",
|
||||
"-368": "Invasion",
|
||||
"-369": "Midsummer",
|
||||
"-370": "Brewfest",
|
||||
"-374": "Noblegarden",
|
||||
"-375": "Pilgrim's Bounty",
|
||||
"-376": "Love is in the Air"
|
||||
};
|
||||
return questTypes[professionId] || `Category ${professionId}`;
|
||||
}
|
||||
|
||||
private getQuestExpansionId(questLevel: number): number {
|
||||
if (questLevel <= 60) return 0; // Classic
|
||||
if (questLevel <= 70) return 1; // TBC
|
||||
return 2; // WotLK
|
||||
}
|
||||
|
||||
public async achievements(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||
const realmName = req.params.realm;
|
||||
const charName = req.params.name;
|
||||
|
|
|
|||
|
|
@ -118,6 +118,13 @@ export interface IFactionDbc {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export interface IAreas {
|
||||
id: number;
|
||||
zoneName: string;
|
||||
mapId: number;
|
||||
areaId: number;
|
||||
}
|
||||
|
||||
interface IAsyncGeneratorWithArrayMethods<T> {
|
||||
[Symbol.asyncIterator](): AsyncGenerator<T>;
|
||||
toArray(): Promise<T[]>;
|
||||
|
|
@ -321,6 +328,7 @@ 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"),
|
||||
areas: path.join(dir, "Areas.csv"),
|
||||
faction: path.join(dir, "Factions.csv"),
|
||||
glyphProperties: path.join(dir, "GlyphProperties_3.3.5_12340.csv"),
|
||||
item: path.join(dir, "Item_3.3.5_12340.csv"),
|
||||
|
|
@ -341,6 +349,7 @@ export const DbcFiles = {
|
|||
const dbcFields = {
|
||||
achievement: ["id", "faction", "titleLang0", "descriptionLang0", "category", "points", "flags", "iconId"],
|
||||
achievementCategory: ["id", "parent", "nameLang0"],
|
||||
areas: ["id", "zoneName", "mapId", "areaId"],
|
||||
glyphProperties: ["id", "spellId"],
|
||||
item: ["id", "classId", "subclassId", "displayInfoId", "inventoryType"],
|
||||
itemRetail: ["id", "inventoryType"],
|
||||
|
|
@ -373,6 +382,7 @@ const dbcFields = {
|
|||
export class DbcManager {
|
||||
private _achievement: IAchievement[];
|
||||
private _achievementCategory: IAchievementCategory[];
|
||||
private _areas: IAreas[];
|
||||
private _glyphProperties: IGlyphProperties[];
|
||||
private _faction: IFactionDbc[];
|
||||
private _item: IItemDbc[];
|
||||
|
|
@ -395,6 +405,7 @@ export class DbcManager {
|
|||
DbcFiles.achievementCategory,
|
||||
dbcFields.achievementCategory,
|
||||
).toArray();
|
||||
this._areas = await this.read<IAreas>(DbcFiles.areas).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();
|
||||
|
|
@ -425,6 +436,9 @@ export class DbcManager {
|
|||
return this.getLoadedDataOrRead(DbcFiles.achievementCategory, this._achievementCategory, dbcFields.achievementCategory);
|
||||
}
|
||||
|
||||
public areas() {
|
||||
return this.getLoadedDataOrRead(DbcFiles.areas, this._areas);
|
||||
}
|
||||
public faction() {
|
||||
return this.getLoadedDataOrRead(DbcFiles.faction, this._faction, dbcFields.faction);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue