mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-06 13:58:05 +03:00
milestone support in one
This commit is contained in:
Generated
+3
-3
@@ -62,7 +62,7 @@ dependencies:
|
||||
express-async-errors: 3.1.1_express@4.17.1
|
||||
express-session: 1.17.1
|
||||
fast-json-stable-hash: 1.0.2
|
||||
kamaitachi-common: github.com/zkldi/Kamaitachi-common/67f10b22a38cee3c270d74d2091466aef45c2318
|
||||
kamaitachi-common: github.com/zkldi/Kamaitachi-common/f10a13f67d186ca9caa474092270f0995ff6c24d
|
||||
mongodb: 3.6.6
|
||||
monk: 7.3.4
|
||||
multer: 1.4.2
|
||||
@@ -4638,8 +4638,8 @@ packages:
|
||||
'@types/yoga-layout': 1.9.2
|
||||
dev: true
|
||||
|
||||
github.com/zkldi/Kamaitachi-common/67f10b22a38cee3c270d74d2091466aef45c2318:
|
||||
resolution: {tarball: https://codeload.github.com/zkldi/Kamaitachi-common/tar.gz/67f10b22a38cee3c270d74d2091466aef45c2318}
|
||||
github.com/zkldi/Kamaitachi-common/f10a13f67d186ca9caa474092270f0995ff6c24d:
|
||||
resolution: {tarball: https://codeload.github.com/zkldi/Kamaitachi-common/tar.gz/f10a13f67d186ca9caa474092270f0995ff6c24d}
|
||||
name: kamaitachi-common
|
||||
version: 0.1.0
|
||||
dependencies:
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import {
|
||||
MilestoneDocument,
|
||||
UserMilestoneDocument,
|
||||
integer,
|
||||
GoalImportInfo,
|
||||
} from "kamaitachi-common";
|
||||
|
||||
/**
|
||||
* Processes and updates a user's milestones from their Goal Import Info (i.e. what is returned
|
||||
* about goals from imports)
|
||||
*/
|
||||
export function ProcessMilestoneFromGII(
|
||||
milestone: MilestoneDocument,
|
||||
gii: Map<string, GoalImportInfo["new"]>
|
||||
) {
|
||||
let goalIDs = GetGoalIDsFromMilestone(milestone);
|
||||
|
||||
let progress = 0;
|
||||
|
||||
for (const goalID of goalIDs) {
|
||||
let userInfo = gii.get(goalID);
|
||||
|
||||
if (!userInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!userInfo.achieved) {
|
||||
continue;
|
||||
}
|
||||
|
||||
progress++;
|
||||
}
|
||||
|
||||
let outOf = CalculateMilestoneOutOf(milestone, goalIDs);
|
||||
|
||||
// milestone achieved!
|
||||
if (progress >= outOf) {
|
||||
return { achieved: true, progress };
|
||||
}
|
||||
|
||||
return { achieved: false, progress };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the goalID documents in a single array from the
|
||||
* nested structure of milestones.
|
||||
*/
|
||||
function GetGoalIDsFromMilestone(milestone: MilestoneDocument) {
|
||||
// this sucks - maybe a nicer way to do this, because nested
|
||||
// maps are just ugly
|
||||
return milestone.milestoneData.map((e) => e.goals.map((e) => e.goalID)).flat(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out how many goals need to be achieved for this
|
||||
* milestone to be considered completed.
|
||||
*/
|
||||
function CalculateMilestoneOutOf(milestone: MilestoneDocument, goalIDs: string[]): integer {
|
||||
if (milestone.criteria.type === "all") {
|
||||
return goalIDs.length;
|
||||
} else if (milestone.criteria.type === "abs") {
|
||||
if (milestone.criteria.value === null) {
|
||||
throw new Error(
|
||||
`Invalid milestone ${milestone.milestoneID} - abs and null are not compatible.`
|
||||
);
|
||||
}
|
||||
|
||||
return milestone.criteria.value!;
|
||||
} else if (milestone.criteria.type === "proportion") {
|
||||
if (milestone.criteria.value === null) {
|
||||
throw new Error(
|
||||
`Invalid milestone ${milestone.milestoneID} - proportion and null are not compatible.`
|
||||
);
|
||||
}
|
||||
|
||||
return Math.floor(milestone.criteria.value * goalIDs.length);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Invalid milestone.criteria.type of ${milestone.criteria.type} -- milestoneID ${milestone.milestoneID}`
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { KtLogger } from "../../../types";
|
||||
import {
|
||||
integer,
|
||||
Game,
|
||||
Playtypes,
|
||||
GoalImportInfo,
|
||||
UserMilestoneDocument,
|
||||
MilestoneImportInfo,
|
||||
} from "kamaitachi-common";
|
||||
import db from "../../../db/db";
|
||||
import { ProcessMilestoneFromGII } from "../../../core/milestone-core";
|
||||
import { BulkWriteUpdateOneOperation } from "mongodb";
|
||||
|
||||
export async function UpdateUsersMilestones(
|
||||
importGoalInfo: GoalImportInfo[],
|
||||
game: Game,
|
||||
playtypes: Playtypes[Game][],
|
||||
userID: integer,
|
||||
logger: KtLogger
|
||||
) {
|
||||
let userGoalInfoMap: Map<string, GoalImportInfo["new"]> = new Map();
|
||||
|
||||
let goalIDs = [];
|
||||
for (const e of importGoalInfo) {
|
||||
userGoalInfoMap.set(e.goalID, e.new);
|
||||
goalIDs.push(e.goalID);
|
||||
}
|
||||
|
||||
let { milestones, userMilestones } = await GetRelevantMilestones(
|
||||
goalIDs,
|
||||
game,
|
||||
playtypes,
|
||||
userID,
|
||||
logger
|
||||
);
|
||||
|
||||
// create a map here to avoid linear searching when
|
||||
// co-iterating
|
||||
let userMilestoneMap = new Map();
|
||||
for (const um of userMilestones) {
|
||||
userMilestoneMap.set(um.milestoneID, um);
|
||||
}
|
||||
|
||||
let importGoalMap = new Map();
|
||||
|
||||
for (const ig of importGoalInfo) {
|
||||
importGoalMap.set(ig.goalID, ig.new);
|
||||
}
|
||||
|
||||
let bwrite: BulkWriteUpdateOneOperation<UserMilestoneDocument>[] = [];
|
||||
|
||||
let importMilestoneInfo: MilestoneImportInfo[] = [];
|
||||
|
||||
for (const milestone of milestones) {
|
||||
let { achieved, progress } = ProcessMilestoneFromGII(milestone, importGoalMap);
|
||||
|
||||
let userMilestone = userMilestoneMap.get(milestone.milestoneID);
|
||||
|
||||
if (!userMilestone) {
|
||||
logger.severe(
|
||||
`Invalid state achieved in milestone processing - processed milestone that user did not have? ${milestone.milestoneID}`
|
||||
);
|
||||
|
||||
throw new Error("Invalid state achieved in milestone processing.");
|
||||
}
|
||||
|
||||
bwrite.push({
|
||||
updateOne: {
|
||||
filter: { milestoneID: milestone.milestoneID },
|
||||
update: {
|
||||
$set: {
|
||||
achieved,
|
||||
progress,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (progress !== userMilestone.progress) {
|
||||
importMilestoneInfo.push({
|
||||
milestoneID: userMilestone.milestoneID,
|
||||
old: {
|
||||
progress: userMilestone.progress,
|
||||
achieved: userMilestone.achieved,
|
||||
},
|
||||
new: {
|
||||
progress,
|
||||
achieved,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (achieved && !userMilestone.achieved) {
|
||||
// @todo emit some sort of event
|
||||
}
|
||||
}
|
||||
|
||||
if (bwrite.length !== 0) {
|
||||
await db["user-milestones"].bulkWrite(bwrite, { ordered: false });
|
||||
}
|
||||
|
||||
return importMilestoneInfo;
|
||||
}
|
||||
|
||||
async function GetRelevantMilestones(
|
||||
goalIDs: string[],
|
||||
game: Game,
|
||||
playtypes: Playtypes[Game][],
|
||||
userID: integer,
|
||||
logger: KtLogger
|
||||
) {
|
||||
let userMilestones = await db["user-milestones"].find({
|
||||
game,
|
||||
playtype: { $in: playtypes },
|
||||
userID,
|
||||
});
|
||||
|
||||
logger.debug(`Found ${userMilestones.length} user-milestones.`);
|
||||
|
||||
let milestones = await db.milestones.find({
|
||||
milestoneID: { $in: userMilestones.map((e) => e.milestoneID) },
|
||||
"milestoneData.goals.goalID": { $in: goalIDs },
|
||||
});
|
||||
|
||||
logger.debug(`Found ${milestones.length} relevant milestones.`);
|
||||
|
||||
return { userMilestones, milestones };
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { ProcessPBs } from "./pb/process-pbs";
|
||||
import { UpdateUsersGamePlaytypeStats } from "./user-game-stats/update-ugs";
|
||||
import db from "../../db/db";
|
||||
import { UpdateUsersGoals } from "./goals/goals";
|
||||
import { UpdateUsersMilestones } from "./milestones/milestones";
|
||||
|
||||
export default async function ScoreImportMain<D, C>(
|
||||
user: PublicUserDocument,
|
||||
@@ -91,7 +92,11 @@ export default async function ScoreImportMain<D, C>(
|
||||
// Evaluate and update the users goals. This returns information about goals that have changed.
|
||||
let goalInfo = await UpdateUsersGoals(game, user.id, chartIDs, logger);
|
||||
|
||||
// --- 8. Finalise Import Document ---
|
||||
// --- 8. Milestones ---
|
||||
// Evaluate and update the users milestones. This returns...
|
||||
let milestoneInfo = await UpdateUsersMilestones(goalInfo, game, playtypes, user.id, logger);
|
||||
|
||||
// --- 9. Finalise Import Document ---
|
||||
// Create and Save an import document to the database, and finish everything up!
|
||||
const ImportDocument: ImportDocument = {
|
||||
importType,
|
||||
@@ -105,6 +110,7 @@ export default async function ScoreImportMain<D, C>(
|
||||
userID: user.id,
|
||||
classDeltas,
|
||||
goalInfo,
|
||||
milestoneInfo,
|
||||
userIntent,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user