feat: initial stats save
All checks were successful
Build / build (18) (pull_request) Successful in 35s
Build / build (20) (pull_request) Successful in 47s
Build / build (22) (pull_request) Successful in 1m16s

This commit is contained in:
AMelonInsideLemon 2025-02-02 20:51:13 +01:00
parent 9823729aa8
commit 3098d3290f
6 changed files with 498 additions and 17 deletions

View File

@ -1,6 +1,14 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getStats, uploadStats } from "@/src/services/statsService";
import { IStatsUpload } from "@/src/types/statTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
const uploadController: RequestHandler = (_req, res) => { const uploadController: RequestHandler = async (req, res) => {
const payload = getJSONfromString<IStatsUpload>(String(req.body));
const accountId = await getAccountIdForRequest(req);
const playerStats = await getStats(accountId);
await uploadStats(playerStats, payload);
res.status(200).end(); res.status(200).end();
}; };

View File

@ -1,32 +1,34 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IStatsView } from "@/src/types/statTypes";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import allScans from "@/static/fixed_responses/allScans.json"; import allScans from "@/static/fixed_responses/allScans.json";
import { ExportEnemies } from "warframe-public-export-plus"; import { ExportEnemies } from "warframe-public-export-plus";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getStats } from "@/src/services/statsService";
import { IStatsView } from "@/src/types/statTypes";
const viewController: RequestHandler = async (req, res) => { const viewController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "XPInfo"); const inventory = await getInventory(accountId, "XPInfo");
const playerStats = await getStats(accountId);
const responseJson: IStatsView = {}; const responseJson: IStatsView = playerStats.toJSON();
responseJson.Weapons = []; responseJson.Weapons ??= [];
for (const item of inventory.XPInfo) { for (const item of inventory.XPInfo) {
responseJson.Weapons.push({ const weaponIndex = responseJson.Weapons.findIndex(element => element.type == item.ItemType);
type: item.ItemType, if (weaponIndex !== -1) {
xp: item.XP responseJson.Weapons[weaponIndex].xp == item.XP;
}); } else {
responseJson.Weapons.push({ type: item.ItemType, xp: item.XP });
}
} }
if (config.unlockAllScans) { if (config.unlockAllScans) {
const scans = new Set(allScans); const scans = new Set(allScans);
for (const type of Object.keys(ExportEnemies.avatars)) { for (const type of Object.keys(ExportEnemies.avatars)) {
if (!scans.has(type)) { if (!scans.has(type)) scans.add(type);
scans.add(type);
}
} }
responseJson.Scans = [];
for (const type of scans) { for (const type of scans) {
responseJson.Scans ??= [];
responseJson.Scans.push({ type: type, scans: 9999 }); responseJson.Scans.push({ type: type, scans: 9999 });
} }
} }

101
src/models/statsModel.ts Normal file
View File

@ -0,0 +1,101 @@
import { Document, Schema, Types, model } from "mongoose";
import { IEnemy, IMission, IScan, ITutorial, IAbility, IWeapon, IStatsDatabase } from "@/src/types/statTypes";
const abilitySchema = new Schema<IAbility>(
{
type: { type: String, required: true },
used: Number
},
{ _id: false }
);
const enemySchema = new Schema<IEnemy>(
{
type: { type: String, required: true },
executions: Number,
headshots: Number,
kills: Number,
assists: Number,
deaths: Number
},
{ _id: false }
);
const missionSchema = new Schema<IMission>(
{
type: { type: String, required: true },
highScore: Number
},
{ _id: false }
);
const scanSchema = new Schema<IScan>(
{
type: { type: String, required: true },
scans: Number
},
{ _id: false }
);
const tutorialSchema = new Schema<ITutorial>(
{
stage: Number
},
{ _id: false }
);
const weaponSchema = new Schema<IWeapon>(
{
type: { type: String, required: true },
equipTime: Number,
hits: Number,
kills: Number,
xp: Number,
assists: Number,
headshots: Number,
fired: Number
},
{ _id: false }
);
const statsSchema = new Schema<IStatsDatabase>({
accountOwnerId: { type: Schema.Types.ObjectId, required: true },
CiphersSolved: Number,
CiphersFailed: Number,
CipherTime: Number,
Weapons: { type: [weaponSchema], default: [] },
Enemies: { type: [enemySchema], default: [] },
MeleeKills: Number,
MissionsCompleted: Number,
MissionsQuit: Number,
MissionsFailed: Number,
TimePlayedSec: Number,
PickupCount: Number,
Tutorial: { type: Map, of: tutorialSchema, default: {} },
Abilities: { type: [abilitySchema], default: [] },
Rating: Number,
Income: Number,
Rank: Number,
PlayerLevel: Number,
Scans: { type: [scanSchema], default: [] },
Missions: { type: [missionSchema], default: [] },
Deaths: Number,
HealCount: Number,
ReviveCount: Number
});
statsSchema.set("toJSON", {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.accountOwnerId;
}
});
export const Stats = model<IStatsDatabase>("Stats", statsSchema);
// eslint-disable-next-line @typescript-eslint/ban-types
export type TStatsDatabaseDocument = Document<unknown, {}, IStatsDatabase> & {
_id: Types.ObjectId;
__v: number;
} & IStatsDatabase;

View File

@ -7,6 +7,7 @@ import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { PersonalRooms } from "@/src/models/personalRoomsModel"; import { PersonalRooms } from "@/src/models/personalRoomsModel";
import { Request } from "express"; import { Request } from "express";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { createStats } from "@/src/services/statsService";
export const isCorrectPassword = (requestPassword: string, databasePassword: string): boolean => { export const isCorrectPassword = (requestPassword: string, databasePassword: string): boolean => {
return requestPassword === databasePassword; return requestPassword === databasePassword;
@ -24,6 +25,7 @@ export const createAccount = async (accountData: IDatabaseAccount): Promise<IDat
const shipId = await createShip(account._id); const shipId = await createShip(account._id);
await createInventory(account._id, { loadOutPresetId: loadoutId, ship: shipId }); await createInventory(account._id, { loadOutPresetId: loadoutId, ship: shipId });
await createPersonalRooms(account._id, shipId); await createPersonalRooms(account._id, shipId);
await createStats(account._id.toString());
return account.toJSON(); return account.toJSON();
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {

View File

@ -0,0 +1,283 @@
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
import { IStatsUpload } from "@/src/types/statTypes";
export const createStats = async (accountId: string): Promise<TStatsDatabaseDocument> => {
const stats = new Stats({ accountOwnerId: accountId });
await stats.save();
return stats;
};
export const getStats = async (accountOwnerId: string): Promise<TStatsDatabaseDocument> => {
let stats = await Stats.findOne({ accountOwnerId: accountOwnerId });
if (!stats) stats = await createStats(accountOwnerId);
return stats;
};
export const uploadStats = async (playerStats: TStatsDatabaseDocument, payload: IStatsUpload): Promise<void> => {
if (payload.add) {
const {
MISSION_COMPLETE,
PICKUP_ITEM,
SCAN,
USE_ABILITY,
FIRE_WEAPON,
HIT_ENTITY_ITEM,
HEADSHOT_ITEM,
KILL_ENEMY_ITEM,
KILL_ENEMY,
EXECUTE_ENEMY,
HEADSHOT,
DIE,
MELEE_KILL,
INCOME,
CIPHER
} = payload.add;
if (MISSION_COMPLETE) {
for (const [key, value] of Object.entries(MISSION_COMPLETE)) {
switch (key) {
case "GS_SUCCESS":
playerStats.MissionsCompleted ??= 0;
playerStats.MissionsCompleted += value;
break;
case "GS_QUIT":
playerStats.MissionsQuit ??= 0;
playerStats.MissionsQuit += value;
break;
case "GS_FAILURE":
playerStats.MissionsFailed ??= 0;
playerStats.MissionsFailed += value;
break;
}
}
}
if (PICKUP_ITEM) {
for (const value of Object.values(PICKUP_ITEM)) {
playerStats.PickupCount ??= 0;
playerStats.PickupCount += value;
}
}
if (SCAN) {
for (const [key, scans] of Object.entries(SCAN)) {
playerStats.Scans ??= [];
const scan = playerStats.Scans.find(element => element.type === key);
if (scan) {
scan.scans ??= 0;
scan.scans += scans;
} else {
playerStats.Scans.push({ type: key, scans });
}
}
}
if (USE_ABILITY) {
for (const [key, used] of Object.entries(USE_ABILITY)) {
playerStats.Abilities ??= [];
const ability = playerStats.Abilities.find(element => element.type === key);
if (ability) {
ability.used ??= 0;
ability.used += used;
} else {
playerStats.Abilities.push({ type: key, used });
}
}
}
if (FIRE_WEAPON) {
for (const [key, fired] of Object.entries(FIRE_WEAPON)) {
playerStats.Weapons ??= [];
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.fired ??= 0;
weapon.fired += fired;
} else {
playerStats.Weapons.push({ type: key, fired });
}
}
}
if (HIT_ENTITY_ITEM) {
for (const [key, hits] of Object.entries(HIT_ENTITY_ITEM)) {
playerStats.Weapons ??= [];
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.hits ??= 0;
weapon.hits += hits;
} else {
playerStats.Weapons.push({ type: key, hits });
}
}
}
if (HEADSHOT_ITEM) {
for (const [key, headshots] of Object.entries(HEADSHOT_ITEM)) {
playerStats.Weapons ??= [];
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.headshots ??= 0;
weapon.headshots += headshots;
} else {
playerStats.Weapons.push({ type: key, headshots });
}
}
}
if (KILL_ENEMY_ITEM) {
for (const [key, kills] of Object.entries(KILL_ENEMY_ITEM)) {
playerStats.Weapons ??= [];
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.kills ??= 0;
weapon.kills += kills;
} else {
playerStats.Weapons.push({ type: key, kills });
}
}
}
if (KILL_ENEMY) {
for (const [key, kills] of Object.entries(KILL_ENEMY)) {
playerStats.Enemies ??= [];
const enemy = playerStats.Enemies.find(element => element.type === key);
if (enemy) {
enemy.kills ??= 0;
enemy.kills += kills;
} else {
playerStats.Enemies.push({ type: key, kills });
}
}
}
if (EXECUTE_ENEMY) {
for (const [key, executions] of Object.entries(EXECUTE_ENEMY)) {
playerStats.Enemies ??= [];
const enemy = playerStats.Enemies.find(element => element.type === key);
if (enemy) {
enemy.executions ??= 0;
enemy.executions += executions;
} else {
playerStats.Enemies.push({ type: key, executions });
}
}
}
if (HEADSHOT) {
for (const [key, headshots] of Object.entries(HEADSHOT)) {
playerStats.Enemies ??= [];
const enemy = playerStats.Enemies.find(element => element.type === key);
if (enemy) {
enemy.headshots ??= 0;
enemy.headshots += headshots;
} else {
playerStats.Enemies.push({ type: key, headshots });
}
}
}
if (DIE) {
for (const [key, deaths] of Object.entries(DIE)) {
playerStats.Deaths ??= 0;
playerStats.Deaths += deaths;
playerStats.Enemies ??= [];
const enemy = playerStats.Enemies.find(element => element.type === key);
if (enemy) {
enemy.deaths ??= 0;
enemy.deaths += deaths;
} else {
playerStats.Enemies.push({ type: key, deaths });
}
}
}
if (MELEE_KILL) {
playerStats.MeleeKills ??= 0;
for (const kills of Object.values(MELEE_KILL)) {
playerStats.MeleeKills += kills;
}
}
if (INCOME) {
playerStats.Income ??= 0;
playerStats.Income += INCOME;
}
if (CIPHER) {
if (CIPHER["0"] > 0) {
playerStats.CiphersFailed ??= 0;
playerStats.CiphersFailed += CIPHER["0"];
}
if (CIPHER["1"] > 0) {
playerStats.CiphersSolved ??= 0;
playerStats.CiphersSolved += CIPHER["1"];
}
}
}
if (payload.timers) {
const { EQUIP_WEAPON, CURRENT_MISSION_TIME, CIPHER_TIME } = payload.timers;
if (EQUIP_WEAPON) {
for (const [key, equipTime] of Object.entries(EQUIP_WEAPON)) {
playerStats.Weapons ??= [];
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.equipTime ??= 0;
weapon.equipTime += equipTime;
} else {
playerStats.Weapons.push({ type: key, equipTime });
}
}
}
if (CURRENT_MISSION_TIME) {
playerStats.TimePlayedSec ??= 0;
playerStats.TimePlayedSec += CURRENT_MISSION_TIME;
}
if (CIPHER_TIME) {
playerStats.CipherTime ??= 0;
playerStats.CipherTime += CIPHER_TIME;
}
}
if (payload.max) {
const { WEAPON_XP, MISSION_SCORE } = payload.max;
if (WEAPON_XP) {
for (const [key, xp] of Object.entries(WEAPON_XP)) {
playerStats.Weapons ??= [];
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.xp = xp;
} else {
playerStats.Weapons.push({ type: key, xp });
}
}
}
if (MISSION_SCORE) {
for (const [key, highScore] of Object.entries(MISSION_SCORE)) {
playerStats.Missions ??= [];
const mission = playerStats.Missions.find(element => element.type === key);
if (mission) {
mission.highScore = highScore;
} else {
playerStats.Missions.push({ type: key, highScore });
}
}
}
}
if (payload.set) {
const { ELO_RATING, RANK, PLAYER_LEVEL } = payload.set;
if (ELO_RATING) playerStats.Rating = ELO_RATING;
if (RANK) playerStats.Rank = RANK;
if (PLAYER_LEVEL) playerStats.PlayerLevel = PLAYER_LEVEL;
}
await playerStats.save();
};

View File

@ -1,3 +1,5 @@
import { Types } from "mongoose";
export interface IStatsView { export interface IStatsView {
CiphersSolved?: number; CiphersSolved?: number;
CiphersFailed?: number; CiphersFailed?: number;
@ -23,28 +25,32 @@ export interface IStatsView {
ReviveCount?: number; ReviveCount?: number;
} }
export interface IStatsDatabase extends IStatsView {
accountOwnerId: Types.ObjectId;
}
export interface IAbility { export interface IAbility {
used: number;
type: string; type: string;
used: number;
} }
export interface IEnemy { export interface IEnemy {
type: string;
executions?: number; executions?: number;
headshots?: number; headshots?: number;
kills?: number; kills?: number;
type: string;
assists?: number; assists?: number;
deaths?: number; deaths?: number;
} }
export interface IMission { export interface IMission {
highScore: number;
type: string; type: string;
highScore: number;
} }
export interface IScan { export interface IScan {
scans: number;
type: string; type: string;
scans: number;
} }
export interface ITutorial { export interface ITutorial {
@ -52,12 +58,91 @@ export interface ITutorial {
} }
export interface IWeapon { export interface IWeapon {
type: string;
equipTime?: number; equipTime?: number;
hits?: number; hits?: number;
kills?: number; kills?: number;
xp?: number; xp?: number;
assists?: number; assists?: number;
type: string;
headshots?: number; headshots?: number;
fired?: number; fired?: number;
} }
export interface IStatsUpload {
displayName: string;
guildId?: string;
PS?: string;
add?: IStatsAdd;
set?: IStatsSet;
max?: IStatsMax;
timers?: IStatsTimers;
}
export interface IStatsAdd {
GEAR_USED?: IUploadEntry;
SCAN?: IUploadEntry;
MISSION_COMPLETE?: IUploadEntry;
HEADSHOT_ITEM?: IUploadEntry;
HEADSHOT?: IUploadEntry;
PLAYER_COUNT?: IUploadEntry;
HOST_MIGRATION?: IUploadEntry;
PICKUP_ITEM?: IUploadEntry;
FIRE_WEAPON?: IUploadEntry;
HIT_ENTITY_ITEM?: IUploadEntry;
DESTROY_DECORATION?: IUploadEntry;
KILL_ENEMY?: IUploadEntry;
TAKE_DAMAGE?: IUploadEntry;
SQUAD_KILL_ENEMY?: IUploadEntry;
RECEIVE_UPGRADE?: IUploadEntry;
USE_ABILITY?: IUploadEntry;
SQUAD_VIP_KILL?: IUploadEntry;
HEAL_BUDDY?: IUploadEntry;
INCOME?: number;
CIPHER?: IUploadEntry;
EQUIP_COSMETIC?: IUploadEntry;
EQUIP_UPGRADE?: IUploadEntry;
KILL_BOSS?: IUploadEntry;
MISSION_TYPE?: IUploadEntry;
MISSION_FACTION?: IUploadEntry;
MISSION_PLAYED?: IUploadEntry;
MISSION_PLAYED_TIME?: IUploadEntry;
MEDALS_TOP?: IUploadEntry;
INPUT_ACTIVITY_TIME?: IUploadEntry;
KILL_ENEMY_ITEM?: IUploadEntry;
TAKE_DAMAGE_ITEM?: IUploadEntry;
SQUAD_KILL_ENEMY_ITEM?: IUploadEntry;
MELEE_KILL?: IUploadEntry;
SQUAD_MELEE_KILL?: IUploadEntry;
MELEE_KILL_ITEM?: IUploadEntry;
SQUAD_MELEE_KILL_ITEM?: IUploadEntry;
DIE?: IUploadEntry;
DIE_ITEM?: IUploadEntry;
EXECUTE_ENEMY?: IUploadEntry;
EXECUTE_ENEMY_ITEM?: IUploadEntry;
}
export interface IUploadEntry {
[key: string]: number;
}
export interface IStatsMax {
WEAPON_XP?: IUploadEntry;
MISSION_SCORE?: IUploadEntry;
}
export interface IStatsSet {
ELO_RATING?: number;
RANK?: number;
PLAYER_LEVEL?: number;
}
export interface IStatsTimers {
IN_SHIP_TIME?: number;
IN_SHIP_VIEW_TIME?: IUploadEntry;
EQUIP_WEAPON?: IUploadEntry;
MISSION_TIME?: IUploadEntry;
REGION_TIME?: IUploadEntry;
PLATFORM_TIME?: IUploadEntry;
CURRENT_MISSION_TIME?: number;
CIPHER_TIME?: number;
}