feat: initial protovyre/evolving cosmetics (#2566)

Basic handling of sending the challenge rewards to the inbox upon completion.

Re #2485. Still missing handling for the Protovyre armor pieces which require killing sentients.

Reviewed-on: OpenWF/SpaceNinjaServer#2566
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-07-30 01:50:23 -07:00 committed by Sainan
parent 522924a823
commit 8e639a16bd
3 changed files with 74 additions and 11 deletions

View File

@ -10,6 +10,7 @@ import { logger } from "@/src/utils/logger";
export const updateChallengeProgressController: RequestHandler = async (req, res) => { export const updateChallengeProgressController: RequestHandler = async (req, res) => {
const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body)); const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
logger.debug(`challenge report:`, challenges);
const inventory = await getInventory( const inventory = await getInventory(
account._id.toString(), account._id.toString(),
@ -17,7 +18,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
); );
let affiliationMods: IAffiliationMods[] = []; let affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress) { if (challenges.ChallengeProgress) {
affiliationMods = addChallenges( affiliationMods = await addChallenges(
account, account,
inventory, inventory,
challenges.ChallengeProgress, challenges.ChallengeProgress,

View File

@ -1904,25 +1904,87 @@ export const addLoreFragmentScans = (inventory: TInventoryDatabaseDocument, arr:
}); });
}; };
export const addChallenges = ( const challengeRewardsInboxMessages: Record<string, IMessageCreationTemplate> = {
SentEvoEphemeraRankOne: {
sub: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockAName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockADesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Effects/NarmerEvolvingEphemeraB"]
},
SentEvoEphemeraRankTwo: {
sub: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockBName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockBDesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Effects/NarmerEvolvingEphemeraC"]
},
SentEvoSyandanaRankOne: {
sub: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockAName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockADesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Scarves/NarmerEvolvingSyandanaBCape"]
},
SentEvoSyandanaRankTwo: {
sub: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockBName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockBDesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Scarves/NarmerEvolvingSyandanaCCape"]
},
SentEvoSekharaRankOne: {
sub: "/Lotus/Language/Inbox/EvolvingSekharaUnlockAName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSekharaUnlockADesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Clan/ZarimanEvolvingSekharaBadgeItemB"]
},
SentEvoSekharaRankTwo: {
sub: "/Lotus/Language/Inbox/EvolvingSekharaUnlockBName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSekharaUnlockBDesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Clan/ZarimanEvolvingSekharaBadgeItemC"]
}
};
export const addChallenges = async (
account: TAccountDocument, account: TAccountDocument,
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
ChallengeProgress: IChallengeProgress[], ChallengeProgress: IChallengeProgress[],
SeasonChallengeCompletions: ISeasonChallenge[] | undefined SeasonChallengeCompletions: ISeasonChallenge[] | undefined
): IAffiliationMods[] => { ): Promise<IAffiliationMods[]> => {
ChallengeProgress.forEach(({ Name, Progress }) => { for (const { Name, Progress, Completed } of ChallengeProgress) {
const itemIndex = inventory.ChallengeProgress.findIndex(i => i.Name === Name); let dbChallenge = inventory.ChallengeProgress.find(x => x.Name == Name);
if (dbChallenge) {
if (itemIndex !== -1) { dbChallenge.Progress = Progress;
inventory.ChallengeProgress[itemIndex].Progress = Progress;
} else { } else {
inventory.ChallengeProgress.push({ Name, Progress }); dbChallenge = { Name, Progress };
inventory.ChallengeProgress.push(dbChallenge);
} }
if (Name.startsWith("Calendar")) { if (Name.startsWith("Calendar")) {
addString(getCalendarProgress(inventory).SeasonProgress.ActivatedChallenges, Name); addString(getCalendarProgress(inventory).SeasonProgress.ActivatedChallenges, Name);
} }
});
if ((Completed?.length ?? 0) > (dbChallenge.Completed?.length ?? 0)) {
dbChallenge.Completed ??= [];
for (const completion of Completed!) {
if (dbChallenge.Completed.indexOf(completion) == -1) {
if (completion == "challengeRewards") {
if (Name in challengeRewardsInboxMessages) {
await createMessage(account._id, [challengeRewardsInboxMessages[Name]]);
dbChallenge.Completed.push(completion);
// Would love to somehow let the client know about inbox or inventory changes, but there doesn't seem to anything for updateChallengeProgress.
continue;
}
}
logger.warn(`ignoring unknown challenge completion`, { challenge: Name, completion });
}
}
}
}
const affiliationMods: IAffiliationMods[] = []; const affiliationMods: IAffiliationMods[] = [];
if (SeasonChallengeCompletions) { if (SeasonChallengeCompletions) {

View File

@ -292,7 +292,7 @@ export const addMissionInventoryUpdates = async (
addRecipes(inventory, value); addRecipes(inventory, value);
break; break;
case "ChallengeProgress": case "ChallengeProgress":
addChallenges(account, inventory, value, inventoryUpdates.SeasonChallengeCompletions); await addChallenges(account, inventory, value, inventoryUpdates.SeasonChallengeCompletions);
break; break;
case "FusionTreasures": case "FusionTreasures":
addFusionTreasures(inventory, value); addFusionTreasures(inventory, value);