Compare commits

...

12 Commits

Author SHA1 Message Date
045d933458 fix: complete junction data and crash in vors prize mission four (#1001)
Reviewed-on: OpenWF/SpaceNinjaServer#1001
Co-authored-by: OrdisPrime <134585663+OrdisPrime@users.noreply.github.com>
Co-committed-by: OrdisPrime <134585663+OrdisPrime@users.noreply.github.com>
2025-02-24 08:50:07 -08:00
9de57668ab fix: ensure quest progress exists (#1000)
Reviewed-on: OpenWF/SpaceNinjaServer#1000
Co-authored-by: OrdisPrime <134585663+OrdisPrime@users.noreply.github.com>
Co-committed-by: OrdisPrime <134585663+OrdisPrime@users.noreply.github.com>
2025-02-24 06:14:47 -08:00
ebb28d56d5 feat: acquisition of resource extractor drones (#998)
Related to #793

Reviewed-on: OpenWF/SpaceNinjaServer#998
Co-authored-by: Sainan <sainan@calamity.inc>
Co-committed-by: Sainan <sainan@calamity.inc>
2025-02-24 05:28:43 -08:00
d69cba6bef chore: reuse inventory in claimCompletedRecipeController (#999)
Reviewed-on: OpenWF/SpaceNinjaServer#999
Co-authored-by: Sainan <sainan@calamity.inc>
Co-committed-by: Sainan <sainan@calamity.inc>
2025-02-24 05:27:50 -08:00
50d687e59a fix: re-enable giving ship features and mission rewards from Vors Prize after skipTutorial (#996)
Reviewed-on: OpenWF/SpaceNinjaServer#996
Co-authored-by: OrdisPrime <134585663+OrdisPrime@users.noreply.github.com>
Co-committed-by: OrdisPrime <134585663+OrdisPrime@users.noreply.github.com>
2025-02-23 12:22:54 -08:00
1274304647 chore: fix type not matching reality 2025-02-23 14:10:10 +01:00
837e041db8 feat: unveil riven with cipher (#992)
Related to #722

Reviewed-on: OpenWF/SpaceNinjaServer#992
Co-authored-by: Sainan <sainan@calamity.inc>
Co-committed-by: Sainan <sainan@calamity.inc>
2025-02-23 03:55:15 -08:00
84d7b5a62e fix: handle droptable giving a 3-day booster (#993)
e.g. sorties can rarely give these

Reviewed-on: OpenWF/SpaceNinjaServer#993
Co-authored-by: Sainan <sainan@calamity.inc>
Co-committed-by: Sainan <sainan@calamity.inc>
2025-02-23 03:54:46 -08:00
3c2d194302 chore: replace fusionBundles map with ExportFusionBundles (#994)
Reviewed-on: OpenWF/SpaceNinjaServer#994
Co-authored-by: Sainan <sainan@calamity.inc>
Co-committed-by: Sainan <sainan@calamity.inc>
2025-02-23 03:54:26 -08:00
e1af6bd598 feat: implement CreditBundle purchases (#989)
This fixes purchasing one of the few bundles that include these credit bundles. Ex: Essential Damage mod bundles

Reviewed-on: OpenWF/SpaceNinjaServer#989
Co-authored-by: nrbdev <itzneonrb@gmail.com>
Co-committed-by: nrbdev <itzneonrb@gmail.com>
2025-02-23 03:53:56 -08:00
0142aa72a8 chore: sort api post routes 2025-02-23 05:00:41 +01:00
02c0c1c2f2 chore: fix imports in api.ts 2025-02-23 04:33:28 +01:00
16 changed files with 360 additions and 187 deletions

8
package-lock.json generated
View File

@ -12,7 +12,7 @@
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"express": "^5", "express": "^5",
"mongoose": "^8.9.4", "mongoose": "^8.9.4",
"warframe-public-export-plus": "^0.5.30", "warframe-public-export-plus": "^0.5.35",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
@ -4093,9 +4093,9 @@
} }
}, },
"node_modules/warframe-public-export-plus": { "node_modules/warframe-public-export-plus": {
"version": "0.5.32", "version": "0.5.35",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.32.tgz", "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.35.tgz",
"integrity": "sha512-jO9i2Gzz9DWibiHlEO17D975ajs6KrTay8cS5I0GkUUe1XWVU8mML4b+IYCHzM4FWq1t6p2YPCGznQfknqvorg==" "integrity": "sha512-YLQP1n5sOV+PS5hfC4Kuoapa9gsqOy5Qy/E4EYfRV/xJBruFl3tPhbdbgFn3HhL2OBrgRJ8yzT5bjIvaHKhOCw=="
}, },
"node_modules/warframe-riven-info": { "node_modules/warframe-riven-info": {
"version": "0.1.2", "version": "0.1.2",

View File

@ -17,7 +17,7 @@
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"express": "^5", "express": "^5",
"mongoose": "^8.9.4", "mongoose": "^8.9.4",
"warframe-public-export-plus": "^0.5.30", "warframe-public-export-plus": "^0.5.35",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"

View File

@ -7,14 +7,7 @@ import { getRecipe } from "@/src/services/itemDataService";
import { IOid } from "@/src/types/commonTypes"; import { IOid } from "@/src/types/commonTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { import { getInventory, updateCurrency, addItem, addMiscItems, addRecipes } from "@/src/services/inventoryService";
getInventory,
updateCurrency,
addItem,
addMiscItems,
addRecipes,
updateCurrencyByAccountId
} from "@/src/services/inventoryService";
export interface IClaimCompletedRecipeRequest { export interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[]; RecipeIds: IOid[];
@ -37,7 +30,6 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
// } // }
inventory.PendingRecipes.pull(pendingRecipe._id); inventory.PendingRecipes.pull(pendingRecipe._id);
await inventory.save();
const recipe = getRecipe(pendingRecipe.ItemType); const recipe = getRecipe(pendingRecipe.ItemType);
if (!recipe) { if (!recipe) {
@ -45,11 +37,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
} }
if (req.query.cancel) { if (req.query.cancel) {
const inventory = await getInventory(accountId);
const currencyChanges = updateCurrency(inventory, recipe.buildPrice * -1, false); const currencyChanges = updateCurrency(inventory, recipe.buildPrice * -1, false);
addMiscItems(inventory, recipe.ingredients); addMiscItems(inventory, recipe.ingredients);
await inventory.save();
await inventory.save();
// Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root. // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
res.json({ res.json({
...currencyChanges, ...currencyChanges,
@ -59,7 +50,6 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
logger.debug("Claiming Recipe", { recipe, pendingRecipe }); logger.debug("Claiming Recipe", { recipe, pendingRecipe });
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") { if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
const inventory = await getInventory(accountId);
inventory.PendingSpectreLoadouts ??= []; inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= []; inventory.SpectreLoadouts ??= [];
@ -77,7 +67,6 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
); );
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]); inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1); inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
await inventory.save();
} }
} }
@ -92,17 +81,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
InventoryChanges = { ...InventoryChanges, Recipes: recipeChanges }; InventoryChanges = { ...InventoryChanges, Recipes: recipeChanges };
const inventory = await getInventory(accountId);
addRecipes(inventory, recipeChanges); addRecipes(inventory, recipeChanges);
await inventory.save();
} }
if (req.query.rush) { if (req.query.rush) {
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,
...(await updateCurrencyByAccountId(recipe.skipBuildTimePrice, true, accountId)) ...updateCurrency(inventory, recipe.skipBuildTimePrice, true)
}; };
} }
const inventory = await getInventory(accountId);
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,
...(await addItem(inventory, recipe.resultType, recipe.num)).InventoryChanges ...(await addItem(inventory, recipe.resultType, recipe.num)).InventoryChanges

View File

@ -0,0 +1,56 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { IUnveiledRivenFingerprint, randomiseRivenStats } from "@/src/helpers/rivenFingerprintHelper";
import { getRandomElement, getRandomInt } from "@/src/services/rngService";
import { ExportUpgrades } from "warframe-public-export-plus";
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = getJSONfromString<ICompleteRandomModChallengeRequest>(String(req.body));
let inventoryChanges: IInventoryChanges = {};
// Remove 20 plat or riven cipher
if ((req.query.p as string) == "1") {
inventoryChanges = { ...updateCurrency(inventory, 20, true) };
} else {
const miscItemChanges: IMiscItem[] = [
{
ItemType: "/Lotus/Types/Items/MiscItems/RivenIdentifier",
ItemCount: -1
}
];
addMiscItems(inventory, miscItemChanges);
inventoryChanges.MiscItems = miscItemChanges;
}
// Update riven fingerprint to a randomised unveiled state
const upgrade = inventory.Upgrades.id(request.ItemId)!;
const meta = ExportUpgrades[upgrade.ItemType];
const fingerprint: IUnveiledRivenFingerprint = {
compat: getRandomElement(meta.compatibleItems!),
lim: 0,
lvl: 0,
lvlReq: getRandomInt(8, 16),
pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"]),
buffs: [],
curses: []
};
randomiseRivenStats(meta, fingerprint);
upgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
Fingerprint: upgrade.UpgradeFingerprint
});
};
interface ICompleteRandomModChallengeRequest {
ItemId: string;
}

View File

@ -2,8 +2,8 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory } from "@/src/services/inventoryService"; import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { IUnveiledRivenFingerprint, randomiseRivenStats } from "@/src/helpers/rivenFingerprintHelper";
import { ExportUpgrades } from "warframe-public-export-plus"; import { ExportUpgrades } from "warframe-public-export-plus";
import { getRandomElement } from "@/src/services/rngService";
export const rerollRandomModController: RequestHandler = async (req, res) => { export const rerollRandomModController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -25,7 +25,7 @@ export const rerollRandomModController: RequestHandler = async (req, res) => {
fingerprint.rerolls++; fingerprint.rerolls++;
upgrade.UpgradeFingerprint = JSON.stringify(fingerprint); upgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
randomiseStats(upgrade.ItemType, fingerprint); randomiseRivenStats(ExportUpgrades[upgrade.ItemType], fingerprint);
upgrade.PendingRerollFingerprint = JSON.stringify(fingerprint); upgrade.PendingRerollFingerprint = JSON.stringify(fingerprint);
await inventory.save(); await inventory.save();
@ -52,28 +52,6 @@ export const rerollRandomModController: RequestHandler = async (req, res) => {
} }
}; };
const randomiseStats = (randomModType: string, fingerprint: IUnveiledRivenFingerprint): void => {
const meta = ExportUpgrades[randomModType];
fingerprint.buffs = [];
const numBuffs = 2 + Math.trunc(Math.random() * 2); // 2 or 3
const buffEntries = meta.upgradeEntries!.filter(x => x.canBeBuff);
for (let i = 0; i != numBuffs; ++i) {
const buffIndex = Math.trunc(Math.random() * buffEntries.length);
const entry = buffEntries[buffIndex];
fingerprint.buffs.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
buffEntries.splice(buffIndex, 1);
}
fingerprint.curses = [];
if (Math.random() < 0.5) {
const entry = getRandomElement(
meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag))
);
fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
}
};
type RerollRandomModRequest = LetsGoGamblingRequest | AwDangitRequest; type RerollRandomModRequest = LetsGoGamblingRequest | AwDangitRequest;
interface LetsGoGamblingRequest { interface LetsGoGamblingRequest {
@ -85,20 +63,4 @@ interface AwDangitRequest {
CommitReroll: boolean; CommitReroll: boolean;
} }
interface IUnveiledRivenFingerprint {
compat: string;
lim: number;
lvl: number;
lvlReq: 0;
rerolls?: number;
pol: string;
buffs: IRivenStat[];
curses: IRivenStat[];
}
interface IRivenStat {
Tag: string;
Value: number;
}
const rerollCosts = [900, 1000, 1200, 1400, 1700, 2000, 2350, 2750, 3150]; const rerollCosts = [900, 1000, 1200, 1400, 1700, 2000, 2350, 2750, 3150];

View File

@ -0,0 +1,38 @@
import { IUpgrade } from "warframe-public-export-plus";
import { getRandomElement } from "../services/rngService";
export interface IUnveiledRivenFingerprint {
compat: string;
lim: 0;
lvl: number;
lvlReq: number;
rerolls?: number;
pol: string;
buffs: IRivenStat[];
curses: IRivenStat[];
}
interface IRivenStat {
Tag: string;
Value: number;
}
export const randomiseRivenStats = (meta: IUpgrade, fingerprint: IUnveiledRivenFingerprint): void => {
fingerprint.buffs = [];
const numBuffs = 2 + Math.trunc(Math.random() * 2); // 2 or 3
const buffEntries = meta.upgradeEntries!.filter(x => x.canBeBuff);
for (let i = 0; i != numBuffs; ++i) {
const buffIndex = Math.trunc(Math.random() * buffEntries.length);
const entry = buffEntries[buffIndex];
fingerprint.buffs.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
buffEntries.splice(buffIndex, 1);
}
fingerprint.curses = [];
if (Math.random() < 0.5) {
const entry = getRandomElement(
meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag))
);
fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
}
};

View File

@ -68,7 +68,9 @@ import {
ICalendarProgress, ICalendarProgress,
IPendingCouponDatabase, IPendingCouponDatabase,
IPendingCouponClient, IPendingCouponClient,
ILibraryAvailableDailyTaskInfo ILibraryAvailableDailyTaskInfo,
IDroneDatabase,
IDroneClient
} from "../../types/inventoryTypes/inventoryTypes"; } from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes"; import { IOid } from "../../types/commonTypes";
import { import {
@ -349,6 +351,27 @@ const TypeXPItemSchema = new Schema<ITypeXPItem>(
{ _id: false } { _id: false }
); );
const droneSchema = new Schema<IDroneDatabase>(
{
ItemType: String,
CurrentHP: Number,
RepairStart: { type: Date, default: undefined }
},
{ id: false }
);
droneSchema.set("toJSON", {
virtuals: true,
transform(_document, obj) {
const client = obj as IDroneClient;
const db = obj as IDroneDatabase;
client.ItemId = toOid(db._id);
delete obj._id;
delete obj.__v;
}
});
const challengeProgressSchema = new Schema<IChallengeProgress>( const challengeProgressSchema = new Schema<IChallengeProgress>(
{ {
Progress: Number, Progress: Number,
@ -509,7 +532,7 @@ const questProgressSchema = new Schema<IQuestStage>(
const questKeysSchema = new Schema<IQuestKeyDatabase>( const questKeysSchema = new Schema<IQuestKeyDatabase>(
{ {
Progress: { type: [questProgressSchema], default: undefined }, Progress: { type: [questProgressSchema], default: [] },
unlock: Boolean, unlock: Boolean,
Completed: Boolean, Completed: Boolean,
CustomData: String, CustomData: String,
@ -1148,8 +1171,8 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
CompletedSorties: [String], CompletedSorties: [String],
LastSortieReward: [Schema.Types.Mixed], LastSortieReward: [Schema.Types.Mixed],
//Resource_Drone[Uselees stuff] // Resource Extractor Drones
Drones: [Schema.Types.Mixed], Drones: [droneSchema],
//Active profile ico //Active profile ico
ActiveAvatarImageType: String, ActiveAvatarImageType: String,
@ -1299,6 +1322,7 @@ export type InventoryDocumentProps = {
PendingRecipes: Types.DocumentArray<IPendingRecipeDatabase>; PendingRecipes: Types.DocumentArray<IPendingRecipeDatabase>;
WeaponSkins: Types.DocumentArray<IWeaponSkinDatabase>; WeaponSkins: Types.DocumentArray<IWeaponSkinDatabase>;
QuestKeys: Types.DocumentArray<IQuestKeyDatabase>; QuestKeys: Types.DocumentArray<IQuestKeyDatabase>;
Drones: Types.DocumentArray<IDroneDatabase>;
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> }; } & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types

View File

@ -3,11 +3,12 @@ import { activateRandomModController } from "@/src/controllers/api/activateRando
import { addFriendImageController } from "@/src/controllers/api/addFriendImageController"; import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController"; import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
import { archonFusionController } from "@/src/controllers/api/archonFusionController"; import { archonFusionController } from "@/src/controllers/api/archonFusionController";
import { artifactsController } from "../controllers/api/artifactsController"; import { artifactsController } from "@/src/controllers/api/artifactsController";
import { changeDojoRootController } from "../controllers/api/changeDojoRootController"; import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootController";
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController"; import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController"; import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController";
import { createGuildController } from "@/src/controllers/api/createGuildController"; import { createGuildController } from "@/src/controllers/api/createGuildController";
import { creditsController } from "@/src/controllers/api/creditsController"; import { creditsController } from "@/src/controllers/api/creditsController";
import { deleteSessionController } from "@/src/controllers/api/deleteSessionController"; import { deleteSessionController } from "@/src/controllers/api/deleteSessionController";
@ -25,7 +26,7 @@ import { getDailyDealStockLevelsController } from "@/src/controllers/api/getDail
import { getFriendsController } from "@/src/controllers/api/getFriendsController"; import { getFriendsController } from "@/src/controllers/api/getFriendsController";
import { getGuildController } from "@/src/controllers/api/getGuildController"; import { getGuildController } from "@/src/controllers/api/getGuildController";
import { getGuildDojoController } from "@/src/controllers/api/getGuildDojoController"; import { getGuildDojoController } from "@/src/controllers/api/getGuildDojoController";
import { getGuildLogController } from "../controllers/api/getGuildLogController"; import { getGuildLogController } from "@/src/controllers/api/getGuildLogController";
import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController"; import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController";
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController"; import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
import { getShipController } from "@/src/controllers/api/getShipController"; import { getShipController } from "@/src/controllers/api/getShipController";
@ -35,7 +36,8 @@ import { gildWeaponController } from "@/src/controllers/api/gildWeaponController
import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
import { giveKeyChainTriggeredMessageController } from "@/src/controllers/api/giveKeyChainTriggeredMessageController"; import { giveKeyChainTriggeredMessageController } from "@/src/controllers/api/giveKeyChainTriggeredMessageController";
import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKey"; import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKey";
import { guildTechController } from "../controllers/api/guildTechController"; import { giveStartingGearController } from "@/src/controllers/api/giveStartingGearController";
import { guildTechController } from "@/src/controllers/api/guildTechController";
import { hostSessionController } from "@/src/controllers/api/hostSessionController"; import { hostSessionController } from "@/src/controllers/api/hostSessionController";
import { hubController } from "@/src/controllers/api/hubController"; import { hubController } from "@/src/controllers/api/hubController";
import { hubInstancesController } from "@/src/controllers/api/hubInstancesController"; import { hubInstancesController } from "@/src/controllers/api/hubInstancesController";
@ -53,12 +55,13 @@ import { modularWeaponCraftingController } from "@/src/controllers/api/modularWe
import { modularWeaponSaleController } from "@/src/controllers/api/modularWeaponSaleController"; import { modularWeaponSaleController } from "@/src/controllers/api/modularWeaponSaleController";
import { nameWeaponController } from "@/src/controllers/api/nameWeaponController"; import { nameWeaponController } from "@/src/controllers/api/nameWeaponController";
import { playerSkillsController } from "@/src/controllers/api/playerSkillsController"; import { playerSkillsController } from "@/src/controllers/api/playerSkillsController";
import { projectionManagerController } from "../controllers/api/projectionManagerController"; import { projectionManagerController } from "@/src/controllers/api/projectionManagerController";
import { purchaseController } from "@/src/controllers/api/purchaseController"; import { purchaseController } from "@/src/controllers/api/purchaseController";
import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController"; import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController"; import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController"; import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
import { saveLoadoutController } from "@/src/controllers/api/saveLoadout"; import { saveLoadoutController } from "@/src/controllers/api/saveLoadout";
import { saveSettingsController } from "@/src/controllers/api/saveSettingsController";
import { sellController } from "@/src/controllers/api/sellController"; import { sellController } from "@/src/controllers/api/sellController";
import { setActiveQuestController } from "@/src/controllers/api/setActiveQuestController"; import { setActiveQuestController } from "@/src/controllers/api/setActiveQuestController";
import { setActiveShipController } from "@/src/controllers/api/setActiveShipController"; import { setActiveShipController } from "@/src/controllers/api/setActiveShipController";
@ -69,25 +72,23 @@ import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDeco
import { setShipCustomizationsController } from "@/src/controllers/api/setShipCustomizationsController"; import { setShipCustomizationsController } from "@/src/controllers/api/setShipCustomizationsController";
import { setShipFavouriteLoadoutController } from "@/src/controllers/api/setShipFavouriteLoadoutController"; import { setShipFavouriteLoadoutController } from "@/src/controllers/api/setShipFavouriteLoadoutController";
import { setSupportedSyndicateController } from "@/src/controllers/api/setSupportedSyndicateController"; import { setSupportedSyndicateController } from "@/src/controllers/api/setSupportedSyndicateController";
import { setWeaponSkillTreeController } from "../controllers/api/setWeaponSkillTreeController"; import { setWeaponSkillTreeController } from "@/src/controllers/api/setWeaponSkillTreeController";
import { shipDecorationsController } from "@/src/controllers/api/shipDecorationsController"; import { shipDecorationsController } from "@/src/controllers/api/shipDecorationsController";
import { startDojoRecipeController } from "@/src/controllers/api/startDojoRecipeController"; import { startDojoRecipeController } from "@/src/controllers/api/startDojoRecipeController";
import { startLibraryPersonalTargetController } from "@/src/controllers/api/startLibraryPersonalTargetController"; import { startLibraryPersonalTargetController } from "@/src/controllers/api/startLibraryPersonalTargetController";
import { startRecipeController } from "@/src/controllers/api/startRecipeController"; import { startRecipeController } from "@/src/controllers/api/startRecipeController";
import { stepSequencersController } from "@/src/controllers/api/stepSequencersController"; import { stepSequencersController } from "@/src/controllers/api/stepSequencersController";
import { surveysController } from "@/src/controllers/api/surveysController"; import { surveysController } from "@/src/controllers/api/surveysController";
import { syndicateSacrificeController } from "../controllers/api/syndicateSacrificeController"; import { syndicateSacrificeController } from "@/src/controllers/api/syndicateSacrificeController";
import { syndicateStandingBonusController } from "../controllers/api/syndicateStandingBonusController"; import { syndicateStandingBonusController } from "@/src/controllers/api/syndicateStandingBonusController";
import { tauntHistoryController } from "@/src/controllers/api/tauntHistoryController"; import { tauntHistoryController } from "@/src/controllers/api/tauntHistoryController";
import { trainingResultController } from "@/src/controllers/api/trainingResultController"; import { trainingResultController } from "@/src/controllers/api/trainingResultController";
import { unlockShipFeatureController } from "@/src/controllers/api/unlockShipFeatureController"; import { unlockShipFeatureController } from "@/src/controllers/api/unlockShipFeatureController";
import { updateChallengeProgressController } from "@/src/controllers/api/updateChallengeProgressController"; import { updateChallengeProgressController } from "@/src/controllers/api/updateChallengeProgressController";
import { updateQuestController } from "@/src/controllers/api/updateQuestController"; import { updateQuestController } from "@/src/controllers/api/updateQuestController";
import { updateSessionGetController, updateSessionPostController } from "@/src/controllers/api/updateSessionController"; import { updateSessionGetController, updateSessionPostController } from "@/src/controllers/api/updateSessionController";
import { updateThemeController } from "../controllers/api/updateThemeController"; import { updateThemeController } from "@/src/controllers/api/updateThemeController";
import { upgradesController } from "@/src/controllers/api/upgradesController"; import { upgradesController } from "@/src/controllers/api/upgradesController";
import { saveSettingsController } from "../controllers/api/saveSettingsController";
import { giveStartingGearController } from "@/src/controllers/api/giveStartingGearController";
const apiRouter = express.Router(); const apiRouter = express.Router();
@ -133,6 +134,7 @@ apiRouter.post("/artifacts.php", artifactsController);
apiRouter.post("/changeDojoRoot.php", changeDojoRootController); apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController); apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
apiRouter.post("/createGuild.php", createGuildController); apiRouter.post("/createGuild.php", createGuildController);
apiRouter.post("/endlessXp.php", endlessXpController); apiRouter.post("/endlessXp.php", endlessXpController);
apiRouter.post("/evolveWeapon.php", evolveWeaponController); apiRouter.post("/evolveWeapon.php", evolveWeaponController);
@ -163,6 +165,7 @@ apiRouter.post("/purchase.php", purchaseController);
apiRouter.post("/rerollRandomMod.php", rerollRandomModController); apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
apiRouter.post("/saveDialogue.php", saveDialogueController); apiRouter.post("/saveDialogue.php", saveDialogueController);
apiRouter.post("/saveLoadout.php", saveLoadoutController); apiRouter.post("/saveLoadout.php", saveLoadoutController);
apiRouter.post("/saveSettings.php", saveSettingsController);
apiRouter.post("/sell.php", sellController); apiRouter.post("/sell.php", sellController);
apiRouter.post("/setDojoComponentMessage.php", setDojoComponentMessageController); apiRouter.post("/setDojoComponentMessage.php", setDojoComponentMessageController);
apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController); apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController);
@ -185,6 +188,5 @@ apiRouter.post("/updateQuest.php", updateQuestController);
apiRouter.post("/updateSession.php", updateSessionPostController); apiRouter.post("/updateSession.php", updateSessionPostController);
apiRouter.post("/updateTheme.php", updateThemeController); apiRouter.post("/updateTheme.php", updateThemeController);
apiRouter.post("/upgrades.php", upgradesController); apiRouter.post("/upgrades.php", upgradesController);
apiRouter.post("/saveSettings.php", saveSettingsController);
export { apiRouter }; export { apiRouter };

View File

@ -24,7 +24,8 @@ import {
IKubrowPetEggDatabase, IKubrowPetEggDatabase,
IKubrowPetEggClient, IKubrowPetEggClient,
ILibraryAvailableDailyTaskInfo, ILibraryAvailableDailyTaskInfo,
ICalendarProgress ICalendarProgress,
IDroneClient
} from "@/src/types/inventoryTypes/inventoryTypes"; } from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate } from "../types/genericUpdate"; import { IGenericUpdate } from "../types/genericUpdate";
import { import {
@ -38,7 +39,9 @@ import { IEquipmentClient, IEquipmentDatabase, IItemConfig } from "../types/inve
import { import {
ExportArcanes, ExportArcanes,
ExportCustoms, ExportCustoms,
ExportDrones,
ExportFlavour, ExportFlavour,
ExportFusionBundles,
ExportGear, ExportGear,
ExportKeys, ExportKeys,
ExportRecipes, ExportRecipes,
@ -50,12 +53,12 @@ import {
TStandingLimitBin TStandingLimitBin
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { createShip } from "./shipService"; import { createShip } from "./shipService";
import { creditBundles, fusionBundles } from "@/src/services/missionInventoryUpdateService"; import { creditBundles } from "@/src/services/missionInventoryUpdateService";
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
import { toOid } from "../helpers/inventoryHelpers"; import { toOid } from "../helpers/inventoryHelpers";
import { generateRewardSeed } from "@/src/controllers/api/getNewRewardSeedController"; import { generateRewardSeed } from "@/src/controllers/api/getNewRewardSeedController";
import { addStartingGear } from "@/src/controllers/api/giveStartingGearController"; import { addStartingGear } from "@/src/controllers/api/giveStartingGearController";
import { completeQuest } from "@/src/services/questService"; import { addQuestKey, completeQuest } from "@/src/services/questService";
export const createInventory = async ( export const createInventory = async (
accountOwnerId: Types.ObjectId, accountOwnerId: Types.ObjectId,
@ -313,8 +316,8 @@ export const addItem = async (
} }
}; };
} }
if (typeName in fusionBundles) { if (typeName in ExportFusionBundles) {
const fusionPointsTotal = fusionBundles[typeName] * quantity; const fusionPointsTotal = ExportFusionBundles[typeName].fusionPoints * quantity;
inventory.FusionPoints += fusionPointsTotal; inventory.FusionPoints += fusionPointsTotal;
return { return {
InventoryChanges: { InventoryChanges: {
@ -324,15 +327,29 @@ export const addItem = async (
} }
if (typeName in ExportKeys) { if (typeName in ExportKeys) {
// Note: "/Lotus/Types/Keys/" contains some EmailItems // Note: "/Lotus/Types/Keys/" contains some EmailItems
inventory.QuestKeys.push({ ItemType: typeName }); const key = ExportKeys[typeName];
if (key.chainStages) {
const key = addQuestKey(inventory, { ItemType: typeName });
if (key) {
return { InventoryChanges: { QuestKeys: [key] } };
}
} else {
const key = { ItemType: typeName, ItemCount: quantity };
const index = inventory.LevelKeys.findIndex(levelKey => levelKey.ItemType == typeName);
if (index) {
inventory.LevelKeys[index].ItemCount += quantity;
} else {
inventory.LevelKeys.push(key);
}
return { InventoryChanges: { LevelKeys: [key] } };
}
}
if (typeName in ExportDrones) {
const inventoryChanges = addDrone(inventory, typeName);
return { return {
InventoryChanges: { InventoryChanges: inventoryChanges
QuestKeys: [
{
ItemType: typeName
}
]
}
}; };
} }
@ -649,20 +666,6 @@ export const updateCurrency = (
return currencyChanges; return currencyChanges;
}; };
export const updateCurrencyByAccountId = async (
price: number,
usePremium: boolean,
accountId: string
): Promise<ICurrencyChanges> => {
if (!isCurrencyTracked(usePremium)) {
return {};
}
const inventory = await getInventory(accountId);
const currencyChanges = updateCurrency(inventory, price, usePremium);
await inventory.save();
return currencyChanges;
};
const standingLimitBinToInventoryKey: Record< const standingLimitBinToInventoryKey: Record<
Exclude<TStandingLimitBin, "STANDING_LIMIT_BIN_NONE">, Exclude<TStandingLimitBin, "STANDING_LIMIT_BIN_NONE">,
keyof IDailyAffiliations keyof IDailyAffiliations
@ -822,6 +825,17 @@ const addMotorcycle = (
return inventoryChanges; return inventoryChanges;
}; };
const addDrone = (
inventory: TInventoryDatabaseDocument,
typeName: string,
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
const index = inventory.Drones.push({ ItemType: typeName, CurrentHP: ExportDrones[typeName].durability }) - 1;
inventoryChanges.Drones ??= [];
inventoryChanges.Drones.push(inventory.Drones[index].toJSON<IDroneClient>());
return inventoryChanges;
};
//TODO: wrong id is not erroring //TODO: wrong id is not erroring
export const addGearExpByCategory = ( export const addGearExpByCategory = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,

View File

@ -163,20 +163,21 @@ export const getKeyChainItems = ({ KeyChain, ChainStage }: IKeyChainRequest): st
}; };
export const getLevelKeyRewards = (levelKey: string) => { export const getLevelKeyRewards = (levelKey: string) => {
const levelKeyData = ExportKeys[levelKey]; if (!ExportKeys[levelKey]) {
if (!levelKeyData) { throw new Error(`LevelKey ${levelKey} not found`);
const error = `LevelKey ${levelKey} not found`;
logger.error(error);
throw new Error(error);
} }
if (!levelKeyData.rewards) { const levelKeyRewards = ExportKeys[levelKey]?.missionReward;
const error = `LevelKey ${levelKey} does not contain rewards`; const levelKeyRewards2 = ExportKeys[levelKey]?.rewards;
logger.error(error);
throw new Error(error); if (!levelKeyRewards && !levelKeyRewards2) {
throw new Error(`LevelKey ${levelKey} does not contain either rewards1 or rewards2`);
} }
return levelKeyData.rewards; return {
levelKeyRewards,
levelKeyRewards2
};
}; };
export const getNode = (nodeName: string): IRegion => { export const getNode = (nodeName: string): IRegion => {

View File

@ -47,16 +47,15 @@ export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Typ
activeShipId: shipId activeShipId: shipId
}); });
if (config.skipTutorial) { if (config.skipTutorial) {
// // Vor's Prize rewards // unlocked during Vor's Prize
// const defaultFeatures = [ const defaultFeatures = [
// "/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem", "/Lotus/Types/Items/ShipFeatureItems/MercuryNavigationFeatureItem",
// "/Lotus/Types/Items/ShipFeatureItems/MercuryNavigationFeatureItem", "/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem",
// "/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem", "/Lotus/Types/Items/ShipFeatureItems/SocialMenuFeatureItem",
// "/Lotus/Types/Items/ShipFeatureItems/SocialMenuFeatureItem", "/Lotus/Types/Items/ShipFeatureItems/FoundryFeatureItem",
// "/Lotus/Types/Items/ShipFeatureItems/FoundryFeatureItem", "/Lotus/Types/Items/ShipFeatureItems/ModsFeatureItem"
// "/Lotus/Types/Items/ShipFeatureItems/ModsFeatureItem" ];
// ]; personalRooms.Ship.Features.push(...defaultFeatures);
// personalRooms.Ship.Features.push(...defaultFeatures);
} }
await personalRooms.save(); await personalRooms.save();
}; };

View File

@ -1,4 +1,10 @@
import { ExportRegions, ExportRewards, IReward } from "warframe-public-export-plus"; import {
ExportFusionBundles,
ExportRegions,
ExportRewards,
IMissionReward as IMissionRewardExternal,
IReward
} from "warframe-public-export-plus";
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes"; import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IRngResult, getRandomReward } from "@/src/services/rngService"; import { IRngResult, getRandomReward } from "@/src/services/rngService";
@ -11,7 +17,6 @@ import {
addFocusXpIncreases, addFocusXpIncreases,
addFusionTreasures, addFusionTreasures,
addGearExpByCategory, addGearExpByCategory,
addItem,
addMiscItems, addMiscItems,
addMissionComplete, addMissionComplete,
addMods, addMods,
@ -26,8 +31,8 @@ import { getLevelKeyRewards, getNode } from "@/src/services/itemDataService";
import { InventoryDocumentProps, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { InventoryDocumentProps, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { getEntriesUnsafe } from "@/src/utils/ts-utils"; import { getEntriesUnsafe } from "@/src/utils/ts-utils";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import junctionRewards from "@/static/fixed_responses/junctionRewards.json"; import { handleStoreItemAcquisition } from "./purchaseService";
import { IJunctionRewards } from "@/src/types/commonTypes"; import { IMissionReward } from "../types/missionTypes";
const getRotations = (rotationCount: number): number[] => { const getRotations = (rotationCount: number): number[] => {
if (rotationCount === 0) return [0]; if (rotationCount === 0) return [0];
@ -62,13 +67,9 @@ export const creditBundles: Record<string, number> = {
"/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardTwoHard": 175000, "/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardTwoHard": 175000,
"/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardThreeHard": 250000, "/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardThreeHard": 250000,
"/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsCommon": 15000, "/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsCommon": 15000,
"/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsUncommon": 30000 "/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsUncommon": 30000,
}; "/Lotus/Types/StoreItems/CreditBundles/CreditBundleA": 50000,
"/Lotus/Types/StoreItems/CreditBundles/CreditBundleC": 175000
export const fusionBundles: Record<string, number> = {
"/Lotus/Upgrades/Mods/FusionBundles/CommonFusionBundle": 15,
"/Lotus/Upgrades/Mods/FusionBundles/UncommonFusionBundle": 50,
"/Lotus/Upgrades/Mods/FusionBundles/RareFusionBundle": 80
}; };
//type TMissionInventoryUpdateKeys = keyof IMissionInventoryUpdateRequest; //type TMissionInventoryUpdateKeys = keyof IMissionInventoryUpdateRequest;
@ -163,7 +164,8 @@ export const addMissionInventoryUpdates = (
case "FusionBundles": { case "FusionBundles": {
let fusionPoints = 0; let fusionPoints = 0;
for (const fusionBundle of value) { for (const fusionBundle of value) {
const fusionPointsTotal = fusionBundles[fusionBundle.ItemType] * fusionBundle.ItemCount; const fusionPointsTotal =
ExportFusionBundles[fusionBundle.ItemType].fusionPoints * fusionBundle.ItemCount;
inventory.FusionPoints += fusionPointsTotal; inventory.FusionPoints += fusionPointsTotal;
fusionPoints += fusionPointsTotal; fusionPoints += fusionPointsTotal;
} }
@ -261,7 +263,11 @@ export const addMissionRewards = async (
if (levelKeyName) { if (levelKeyName) {
const fixedLevelRewards = getLevelKeyRewards(levelKeyName); const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`); //logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
for (const reward of fixedLevelRewards) { if (fixedLevelRewards.levelKeyRewards) {
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards);
}
if (fixedLevelRewards.levelKeyRewards2) {
for (const reward of fixedLevelRewards.levelKeyRewards2) {
//quest stage completion credit rewards //quest stage completion credit rewards
if (reward.rewardType == "RT_CREDITS") { if (reward.rewardType == "RT_CREDITS") {
inventory.RegularCredits += reward.amount; inventory.RegularCredits += reward.amount;
@ -274,43 +280,32 @@ export const addMissionRewards = async (
}); });
} }
} }
if (rewardInfo.node in junctionRewards) {
const junctionReward = (junctionRewards as IJunctionRewards)[rewardInfo.node];
for (const item of junctionReward.items) {
MissionRewards.push({
StoreItem: item.ItemType,
ItemCount: item.ItemCount
});
}
if (junctionReward.credits) {
inventory.RegularCredits += junctionReward.credits;
missionCompletionCredits += junctionReward.credits;
}
} }
for (const reward of MissionRewards) {
//TODO: additem should take in storeItems
const inventoryChange = await addItem(inventory, reward.StoreItem.replace("StoreItems/", ""), reward.ItemCount);
//TODO: combineInventoryChanges improve type safety, merging 2 of the same item?
//TODO: check for the case when two of the same item are added, combineInventoryChanges should merge them, but the client also merges them
//TODO: some conditional types to rule out binchanges?
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
}
//node based credit rewards for mission completion
if (missions) { if (missions) {
const node = getNode(missions.Tag); const node = getNode(missions.Tag);
//node based credit rewards for mission completion
if (node.missionIndex !== 28) { if (node.missionIndex !== 28) {
const levelCreditReward = getLevelCreditRewards(missions?.Tag); const levelCreditReward = getLevelCreditRewards(missions?.Tag);
missionCompletionCredits += levelCreditReward; missionCompletionCredits += levelCreditReward;
inventory.RegularCredits += levelCreditReward; inventory.RegularCredits += levelCreditReward;
logger.debug(`levelCreditReward ${levelCreditReward}`); logger.debug(`levelCreditReward ${levelCreditReward}`);
} }
if (node.missionReward) {
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards);
}
}
for (const reward of MissionRewards) {
const inventoryChange = await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount);
//TODO: combineInventoryChanges improve type safety, merging 2 of the same item?
//TODO: check for the case when two of the same item are added, combineInventoryChanges should merge them, but the client also merges them
//TODO: some conditional types to rule out binchanges?
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
} }
//creditBonus is not correct for mirage mission 3
const credits = addCredits(inventory, { const credits = addCredits(inventory, {
missionCompletionCredits, missionCompletionCredits,
missionDropCredits: creditDrops ?? 0, missionDropCredits: creditDrops ?? 0,
@ -320,7 +315,7 @@ export const addMissionRewards = async (
return { inventoryChanges, MissionRewards, credits }; return { inventoryChanges, MissionRewards, credits };
}; };
//slightly inaccurate compared to official //creditBonus is not entirely accurate.
//TODO: consider ActiveBoosters //TODO: consider ActiveBoosters
export const addCredits = ( export const addCredits = (
inventory: HydratedDocument<IInventoryDatabase>, inventory: HydratedDocument<IInventoryDatabase>,
@ -352,6 +347,40 @@ export const addCredits = (
return { ...finalCredits, DailyMissionBonus: true }; return { ...finalCredits, DailyMissionBonus: true };
}; };
export const addFixedLevelRewards = (
rewards: IMissionRewardExternal,
inventory: TInventoryDatabaseDocument,
MissionRewards: IMissionReward[]
) => {
let missionBonusCredits = 0;
if (rewards.credits) {
missionBonusCredits += rewards.credits;
inventory.RegularCredits += rewards.credits;
}
if (rewards.items) {
for (const item of rewards.items) {
MissionRewards.push({
StoreItem: `/Lotus/StoreItems${item.substring("Lotus/".length)}`,
ItemCount: 1
});
}
}
if (rewards.countedItems) {
for (const item of rewards.countedItems) {
MissionRewards.push({
StoreItem: `/Lotus/StoreItems${item.ItemType.substring("Lotus/".length)}`,
ItemCount: item.ItemCount
});
}
}
if (rewards.countedStoreItems) {
for (const item of rewards.countedStoreItems) {
MissionRewards.push(item);
}
}
return missionBonusCredits;
};
function getLevelCreditRewards(nodeName: string): number { function getLevelCreditRewards(nodeName: string): number {
const minEnemyLevel = getNode(nodeName).minEnemyLevel; const minEnemyLevel = getNode(nodeName).minEnemyLevel;

View File

@ -25,6 +25,7 @@ import {
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { config } from "./configService"; import { config } from "./configService";
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { creditBundles } from "./missionInventoryUpdateService";
export const getStoreItemCategory = (storeItem: string): string => { export const getStoreItemCategory = (storeItem: string): string => {
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/"); const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
@ -330,6 +331,22 @@ const handleBoosterPackPurchase = async (
return purchaseResponse; return purchaseResponse;
}; };
const handleCreditBundlePurchase = async (
typeName: string,
inventory: TInventoryDatabaseDocument
): Promise<IPurchaseResponse> => {
if (typeName && typeName in creditBundles) {
const creditsAmount = creditBundles[typeName];
inventory.RegularCredits += creditsAmount;
await inventory.save();
return { InventoryChanges: { RegularCredits: creditsAmount } };
} else {
throw new Error(`unknown credit bundle: ${typeName}`);
}
};
//TODO: change to getInventory, apply changes then save at the end //TODO: change to getInventory, apply changes then save at the end
const handleTypesPurchase = async ( const handleTypesPurchase = async (
typesName: string, typesName: string,
@ -345,6 +362,8 @@ const handleTypesPurchase = async (
return handleBoosterPackPurchase(typesName, inventory, quantity); return handleBoosterPackPurchase(typesName, inventory, quantity);
case "SlotItems": case "SlotItems":
return handleSlotPurchase(typesName, inventory, quantity); return handleSlotPurchase(typesName, inventory, quantity);
case "CreditBundles":
return handleCreditBundlePurchase(typesName, inventory);
} }
}; };

View File

@ -3,12 +3,18 @@ import { isEmptyObject } from "@/src/helpers/general";
import { IMessage } from "@/src/models/inboxModel"; import { IMessage } from "@/src/models/inboxModel";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { createMessage } from "@/src/services/inboxService"; import { createMessage } from "@/src/services/inboxService";
import { addKeyChainItems } from "@/src/services/inventoryService"; import { addItem, addKeyChainItems } from "@/src/services/inventoryService";
import { getKeyChainMessage } from "@/src/services/itemDataService"; import { getKeyChainMessage, getLevelKeyRewards } from "@/src/services/itemDataService";
import { IInventoryDatabase, IQuestKeyDatabase, IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes"; import {
IInventoryDatabase,
IQuestKeyClient,
IQuestKeyDatabase,
IQuestStage
} from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { HydratedDocument } from "mongoose"; import { HydratedDocument } from "mongoose";
import { ExportKeys } from "warframe-public-export-plus"; import { ExportKeys } from "warframe-public-export-plus";
import { addFixedLevelRewards } from "./missionInventoryUpdateService";
export interface IUpdateQuestRequest { export interface IUpdateQuestRequest {
QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[]; QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[];
@ -69,12 +75,14 @@ export const updateQuestStage = (
Object.assign(questStage, questStageUpdate); Object.assign(questStage, questStageUpdate);
}; };
export const addQuestKey = (inventory: TInventoryDatabaseDocument, questKey: IQuestKeyDatabase): void => { export const addQuestKey = (inventory: TInventoryDatabaseDocument, questKey: IQuestKeyDatabase) => {
if (inventory.QuestKeys.some(q => q.ItemType === questKey.ItemType)) { if (inventory.QuestKeys.some(q => q.ItemType === questKey.ItemType)) {
logger.error(`quest key ${questKey.ItemType} already exists`); logger.error(`quest key ${questKey.ItemType} already exists`);
return; return;
} }
inventory.QuestKeys.push(questKey); const index = inventory.QuestKeys.push(questKey);
return inventory.QuestKeys[index - 1].toJSON<IQuestKeyClient>();
}; };
export const completeQuest = async (inventory: TInventoryDatabaseDocument, questKey: string) => { export const completeQuest = async (inventory: TInventoryDatabaseDocument, questKey: string) => {
@ -125,6 +133,32 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
ChainStage: i ChainStage: i
}); });
} }
const missionName = chainStages[i].key;
if (missionName) {
const fixedLevelRewards = getLevelKeyRewards(missionName);
//logger.debug(`fixedLevelRewards`, fixedLevelRewards);
if (fixedLevelRewards.levelKeyRewards) {
const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards);
for (const reward of missionRewards) {
await addItem(inventory, reward.StoreItem.replace("StoreItems/", ""), reward.ItemCount);
}
} else if (fixedLevelRewards.levelKeyRewards2) {
for (const reward of fixedLevelRewards.levelKeyRewards2) {
if (reward.rewardType == "RT_CREDITS") {
inventory.RegularCredits += reward.amount;
continue;
}
if (reward.rewardType == "RT_RESOURCE") {
await addItem(inventory, reward.itemType.replace("StoreItems/", ""), reward.amount);
} else {
await addItem(inventory, reward.itemType.replace("StoreItems/", ""));
}
}
}
}
} }
inventory.ActiveQuest = ""; inventory.ActiveQuest = "";
//TODO: handle quest completion items //TODO: handle quest completion items

View File

@ -39,6 +39,7 @@ export interface IInventoryDatabase
| "DialogueHistory" | "DialogueHistory"
| "KubrowPetEggs" | "KubrowPetEggs"
| "PendingCoupon" | "PendingCoupon"
| "Drones"
| TEquipmentKey | TEquipmentKey
>, >,
InventoryDatabaseEquipment { InventoryDatabaseEquipment {
@ -62,7 +63,8 @@ export interface IInventoryDatabase
InfestedFoundry?: IInfestedFoundryDatabase; InfestedFoundry?: IInfestedFoundryDatabase;
DialogueHistory?: IDialogueHistoryDatabase; DialogueHistory?: IDialogueHistoryDatabase;
KubrowPetEggs?: IKubrowPetEggDatabase[]; KubrowPetEggs?: IKubrowPetEggDatabase[];
PendingCoupon: IPendingCouponDatabase; PendingCoupon?: IPendingCouponDatabase;
Drones: IDroneDatabase[];
} }
export interface IQuestKeyDatabase { export interface IQuestKeyDatabase {
@ -258,7 +260,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Alignment: IAlignment; Alignment: IAlignment;
CompletedSorties: string[]; CompletedSorties: string[];
LastSortieReward: ILastSortieReward[]; LastSortieReward: ILastSortieReward[];
Drones: IDrone[]; Drones: IDroneClient[];
StepSequencers: IStepSequencer[]; StepSequencers: IStepSequencer[];
ActiveAvatarImageType: string; ActiveAvatarImageType: string;
ShipDecorations: IConsumable[]; ShipDecorations: IConsumable[];
@ -320,7 +322,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
CollectibleSeries: ICollectibleSery[]; CollectibleSeries: ICollectibleSery[];
LibraryAvailableDailyTaskInfo: ILibraryAvailableDailyTaskInfo; LibraryAvailableDailyTaskInfo: ILibraryAvailableDailyTaskInfo;
HasResetAccount: boolean; HasResetAccount: boolean;
PendingCoupon: IPendingCouponClient; PendingCoupon?: IPendingCouponClient;
Harvestable: boolean; Harvestable: boolean;
DeathSquadable: boolean; DeathSquadable: boolean;
EndlessXP?: IEndlessXpProgress[]; EndlessXP?: IEndlessXpProgress[];
@ -508,13 +510,20 @@ export interface IDiscoveredMarker {
discoveryState: number[]; discoveryState: number[];
} }
export interface IDrone { export interface IDroneClient {
ItemType: string; ItemType: string;
CurrentHP: number; CurrentHP: number;
ItemId: IOid; ItemId: IOid;
RepairStart?: IMongoDate; RepairStart?: IMongoDate;
} }
export interface IDroneDatabase {
ItemType: string;
CurrentHP: number;
_id: Types.ObjectId;
RepairStart?: Date;
}
export interface ITypeXPItem { export interface ITypeXPItem {
ItemType: string; ItemType: string;
XP: number; XP: number;

View File

@ -1,5 +1,5 @@
import { IEquipmentClient } from "./inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "./inventoryTypes/commonInventoryTypes";
import { IInfestedFoundryClient, TEquipmentKey } from "./inventoryTypes/inventoryTypes"; import { IDroneClient, IInfestedFoundryClient, TEquipmentKey } from "./inventoryTypes/inventoryTypes";
export interface IPurchaseRequest { export interface IPurchaseRequest {
PurchaseParams: IPurchaseParams; PurchaseParams: IPurchaseParams;
@ -32,10 +32,10 @@ export type IInventoryChanges = {
[_ in SlotNames]?: IBinChanges; [_ in SlotNames]?: IBinChanges;
} & { } & {
[_ in TEquipmentKey]?: IEquipmentClient[]; [_ in TEquipmentKey]?: IEquipmentClient[];
} & ICurrencyChanges & { InfestedFoundry?: IInfestedFoundryClient } & Record< } & ICurrencyChanges & {
string, InfestedFoundry?: IInfestedFoundryClient;
IBinChanges | number | object[] | IInfestedFoundryClient Drones?: IDroneClient[];
>; } & Record<string, IBinChanges | number | object[] | IInfestedFoundryClient>;
export interface IAffiliationMods { export interface IAffiliationMods {
Tag: string; Tag: string;