forked from OpenWF/SpaceNinjaServer
feat: Quests1 (#852)
This commit is contained in:
parent
8858b15693
commit
ef2708b510
1649
package-lock.json
generated
1649
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
56
src/controllers/api/giveKeyChainTriggeredItemsController.ts
Normal file
56
src/controllers/api/giveKeyChainTriggeredItemsController.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { isEmptyObject, parseString } from "@/src/helpers/general";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addKeyChainItems, getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
|
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = parseString(req.query.accountId);
|
||||||
|
const keyChainTriggeredItemsRequest = getJSONfromString(
|
||||||
|
(req.body as string).toString()
|
||||||
|
) as IGiveKeyChainTriggeredItemsRequest;
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const inventoryChanges = await addKeyChainItems(inventory, keyChainTriggeredItemsRequest);
|
||||||
|
|
||||||
|
if (isEmptyObject(inventoryChanges)) {
|
||||||
|
throw new Error("inventory changes was empty after getting keychain items: should not happen");
|
||||||
|
}
|
||||||
|
// items were added: update quest stage's i (item was given)
|
||||||
|
const quest = inventory.QuestKeys.find(quest => quest.ItemType === keyChainTriggeredItemsRequest.KeyChain);
|
||||||
|
|
||||||
|
if (!quest) {
|
||||||
|
throw new Error(`Quest ${keyChainTriggeredItemsRequest.KeyChain} not found in QuestKeys`);
|
||||||
|
}
|
||||||
|
if (!quest.Progress) {
|
||||||
|
throw new Error(`Progress should always exist when giving keychain triggered items`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const questStage = quest.Progress[keyChainTriggeredItemsRequest.ChainStage];
|
||||||
|
if (questStage) {
|
||||||
|
questStage.i = true;
|
||||||
|
} else {
|
||||||
|
const questStageIndex = quest.Progress.push({ i: true }) - 1;
|
||||||
|
if (questStageIndex !== keyChainTriggeredItemsRequest.ChainStage) {
|
||||||
|
throw new Error(
|
||||||
|
`Quest stage index mismatch: ${questStageIndex} !== ${keyChainTriggeredItemsRequest.ChainStage}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.send(inventoryChanges);
|
||||||
|
|
||||||
|
//TODO: Check whether Wishlist is used to track items which should exist uniquely in the inventory
|
||||||
|
/*
|
||||||
|
some items are added or removed (not sure) to the wishlist, in that case a
|
||||||
|
WishlistChanges: ["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],
|
||||||
|
is added to the response, need to determine for which items this is the case and what purpose this has.
|
||||||
|
*/
|
||||||
|
//{"KeyChain":"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain","ChainStage":0}
|
||||||
|
//{"WishlistChanges":["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],"MiscItems":[{"ItemType":"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem","ItemCount":1}]}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IGiveKeyChainTriggeredItemsRequest {
|
||||||
|
KeyChain: string;
|
||||||
|
ChainStage: number;
|
||||||
|
}
|
@ -22,8 +22,6 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
|
|||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
//const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
|
//const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
|
||||||
|
|
||||||
//console.log(body);
|
|
||||||
|
|
||||||
//TODO: check which slot was purchased because pvpBonus is also possible
|
//TODO: check which slot was purchased because pvpBonus is also possible
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
@ -31,7 +29,5 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
|
|||||||
updateSlots(inventory, InventorySlot.PVE_LOADOUTS, 1, 1);
|
updateSlots(inventory, InventorySlot.PVE_LOADOUTS, 1, 1);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
//console.log({ InventoryChanges: currencyChanges }, " added loadout changes:");
|
|
||||||
|
|
||||||
res.json({ InventoryChanges: currencyChanges });
|
res.json({ InventoryChanges: currencyChanges });
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { missionInventoryUpdate } from "@/src/services/inventoryService";
|
|
||||||
import { combineRewardAndLootInventory, getRewards } from "@/src/services/missionInventoryUpdateService";
|
|
||||||
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 { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
|
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import {
|
||||||
|
addMissionInventoryUpdates,
|
||||||
|
addMissionRewards,
|
||||||
|
calculateFinalCredits
|
||||||
|
} from "@/src/services/missionInventoryUpdateService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
/*
|
/*
|
||||||
**** INPUT ****
|
**** INPUT ****
|
||||||
- [ ] crossPlaySetting
|
- [ ] crossPlaySetting
|
||||||
@ -30,13 +33,13 @@ import { logger } from "@/src/utils/logger";
|
|||||||
- [ ] hosts
|
- [ ] hosts
|
||||||
- [x] ChallengeProgress
|
- [x] ChallengeProgress
|
||||||
- [ ] SeasonChallengeHistory
|
- [ ] SeasonChallengeHistory
|
||||||
- [ ] PS (Passive anti-cheat data which includes your username, module list, process list, and system name.)
|
- [ ] PS (anticheat data)
|
||||||
- [ ] ActiveDojoColorResearch
|
- [ ] ActiveDojoColorResearch
|
||||||
- [x] RewardInfo
|
- [x] RewardInfo
|
||||||
- [ ] ReceivedCeremonyMsg
|
- [ ] ReceivedCeremonyMsg
|
||||||
- [ ] LastCeremonyResetDate
|
- [ ] LastCeremonyResetDate
|
||||||
- [ ] MissionPTS (Used to validate the mission/alive time above.)
|
- [ ] MissionPTS (Used to validate the mission/alive time above.)
|
||||||
- [ ] RepHash (A hash from the replication manager/RepMgr Unknown what it does.)
|
- [ ] RepHash
|
||||||
- [ ] EndOfMatchUpload
|
- [ ] EndOfMatchUpload
|
||||||
- [ ] ObjectiveReached
|
- [ ] ObjectiveReached
|
||||||
- [ ] FpsAvg
|
- [ ] FpsAvg
|
||||||
@ -45,34 +48,52 @@ import { logger } from "@/src/utils/logger";
|
|||||||
- [ ] FpsSamples
|
- [ ] FpsSamples
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
|
||||||
try {
|
const missionReport = getJSONfromString((req.body as string).toString()) as IMissionInventoryUpdateRequest;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
|
|
||||||
const lootInventory = getJSONfromString(req.body.toString()) as IMissionInventoryUpdateRequest;
|
|
||||||
|
|
||||||
logger.debug("missionInventoryUpdate with lootInventory =", lootInventory);
|
if (missionReport.MissionStatus !== "GS_SUCCESS") {
|
||||||
|
console.log(`Mission failed: ${missionReport.RewardInfo?.node}`);
|
||||||
const { InventoryChanges, MissionRewards } = getRewards(lootInventory);
|
//todo: return expected response for failed mission
|
||||||
|
res.json([]);
|
||||||
const { combinedInventoryChanges, TotalCredits, CreditsBonus, MissionCredits, FusionPoints } =
|
//duvirisadjob does not provide missionStatus
|
||||||
combineRewardAndLootInventory(InventoryChanges, lootInventory);
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const InventoryJson = JSON.stringify(await missionInventoryUpdate(combinedInventoryChanges, accountId));
|
|
||||||
res.json({
|
|
||||||
// InventoryJson, // this part will reset game data and missions will be locked
|
|
||||||
MissionRewards,
|
|
||||||
InventoryChanges,
|
|
||||||
TotalCredits,
|
|
||||||
CreditsBonus,
|
|
||||||
MissionCredits,
|
|
||||||
FusionPoints
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error parsing JSON data:", err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
|
||||||
|
const missionRewardsResults = await addMissionRewards(inventory, missionReport);
|
||||||
|
|
||||||
|
if (!missionRewardsResults) {
|
||||||
|
console.error("Failed to add mission rewards");
|
||||||
|
res.status(500).json({ error: "Failed to add mission rewards" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { MissionRewards, inventoryChanges, missionCompletionCredits } = missionRewardsResults;
|
||||||
|
|
||||||
|
const inventoryUpdates = addMissionInventoryUpdates(inventory, missionReport);
|
||||||
|
|
||||||
|
//todo ? can go after not awaiting
|
||||||
|
//creditBonus is not correct for mirage mission 3
|
||||||
|
const credits = calculateFinalCredits(inventory, {
|
||||||
|
missionCompletionCredits,
|
||||||
|
missionDropCredits: missionReport.RegularCredits ?? 0,
|
||||||
|
rngRewardCredits: inventoryChanges.RegularCredits as number
|
||||||
|
});
|
||||||
|
|
||||||
|
const InventoryJson = JSON.stringify((await inventory.save()).toJSON());
|
||||||
|
|
||||||
|
//TODO: figure out when to send inventory. it is needed for many cases.
|
||||||
|
res.json({
|
||||||
|
InventoryJson,
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
MissionRewards,
|
||||||
|
...credits,
|
||||||
|
...inventoryUpdates,
|
||||||
|
FusionPoints: inventoryChanges.FusionPoints
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -85,5 +106,3 @@ const missionInventoryUpdateController: RequestHandler = async (req, res): Promi
|
|||||||
- [x] InventoryChanges
|
- [x] InventoryChanges
|
||||||
- [x] FusionPoints
|
- [x] FusionPoints
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { missionInventoryUpdateController };
|
|
||||||
|
12
src/controllers/api/unlockShipFeatureController.ts
Normal file
12
src/controllers/api/unlockShipFeatureController.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { updateShipFeature } from "@/src/services/personalRoomsService";
|
||||||
|
import { IUnlockShipFeatureRequest } from "@/src/types/requestTypes";
|
||||||
|
import { parseString } from "@/src/helpers/general";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
export const unlockShipFeatureController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = parseString(req.query.accountId);
|
||||||
|
const shipFeatureRequest = JSON.parse((req.body as string).toString()) as IUnlockShipFeatureRequest;
|
||||||
|
await updateShipFeature(accountId, shipFeatureRequest.Feature);
|
||||||
|
res.send([]);
|
||||||
|
};
|
41
src/controllers/api/updateQuestController.ts
Normal file
41
src/controllers/api/updateQuestController.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { parseString } from "@/src/helpers/general";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { updateQuestKey, IUpdateQuestRequest } from "@/src/services/questService";
|
||||||
|
import { getQuestCompletionItems } from "@/src/services/itemDataService";
|
||||||
|
import { addItem, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
export const updateQuestController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = parseString(req.query.accountId);
|
||||||
|
const updateQuestRequest = getJSONfromString((req.body as string).toString()) as IUpdateQuestRequest;
|
||||||
|
|
||||||
|
// updates should be made only to one quest key per request
|
||||||
|
if (updateQuestRequest.QuestKeys.length > 1) {
|
||||||
|
throw new Error(`quest keys array should only have 1 item, but has ${updateQuestRequest.QuestKeys.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
|
||||||
|
updateQuestKey(inventory, updateQuestRequest.QuestKeys);
|
||||||
|
|
||||||
|
if (updateQuestRequest.QuestKeys[0].Completed) {
|
||||||
|
logger.debug(`completed quest ${updateQuestRequest.QuestKeys[0].ItemType} `);
|
||||||
|
const questKeyName = updateQuestRequest.QuestKeys[0].ItemType;
|
||||||
|
const questCompletionItems = getQuestCompletionItems(questKeyName);
|
||||||
|
|
||||||
|
logger.debug(`quest completion items { ${questCompletionItems.map(item => item.ItemType).join(", ")} }`);
|
||||||
|
|
||||||
|
const inventoryChanges = {};
|
||||||
|
for (const item of questCompletionItems) {
|
||||||
|
const inventoryDelta = await addItem(inventory, item.ItemType, item.ItemCount);
|
||||||
|
combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges);
|
||||||
|
}
|
||||||
|
res.json({ MissionRewards: [], inventoryChanges });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.send({ MissionRewards: [] });
|
||||||
|
};
|
@ -26,9 +26,9 @@ import {
|
|||||||
IInfestedFoundryDatabase,
|
IInfestedFoundryDatabase,
|
||||||
IHelminthResource,
|
IHelminthResource,
|
||||||
IConsumedSuit,
|
IConsumedSuit,
|
||||||
IQuestProgress,
|
IQuestStage,
|
||||||
IQuestKeyDatabase,
|
IQuestKeyDatabase,
|
||||||
IQuestKeyResponse,
|
IQuestKeyClient,
|
||||||
IFusionTreasure,
|
IFusionTreasure,
|
||||||
ISpectreLoadout,
|
ISpectreLoadout,
|
||||||
IWeaponSkinDatabase,
|
IWeaponSkinDatabase,
|
||||||
@ -518,7 +518,7 @@ infestedFoundrySchema.set("toJSON", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const questProgressSchema = new Schema<IQuestProgress>({
|
const questProgressSchema = new Schema<IQuestStage>({
|
||||||
c: Number,
|
c: Number,
|
||||||
i: Boolean,
|
i: Boolean,
|
||||||
m: Boolean,
|
m: Boolean,
|
||||||
@ -527,7 +527,7 @@ const questProgressSchema = new Schema<IQuestProgress>({
|
|||||||
|
|
||||||
const questKeysSchema = new Schema<IQuestKeyDatabase>(
|
const questKeysSchema = new Schema<IQuestKeyDatabase>(
|
||||||
{
|
{
|
||||||
Progress: [questProgressSchema],
|
Progress: { type: [questProgressSchema], default: undefined },
|
||||||
unlock: Boolean,
|
unlock: Boolean,
|
||||||
Completed: Boolean,
|
Completed: Boolean,
|
||||||
//CustomData: Schema.Types.Mixed,
|
//CustomData: Schema.Types.Mixed,
|
||||||
@ -544,7 +544,7 @@ questKeysSchema.set("toJSON", {
|
|||||||
const questKeysDatabase = ret as IQuestKeyDatabase;
|
const questKeysDatabase = ret as IQuestKeyDatabase;
|
||||||
|
|
||||||
if (questKeysDatabase.CompletionDate) {
|
if (questKeysDatabase.CompletionDate) {
|
||||||
(questKeysDatabase as IQuestKeyResponse).CompletionDate = toMongoDate(questKeysDatabase.CompletionDate);
|
(questKeysDatabase as IQuestKeyClient).CompletionDate = toMongoDate(questKeysDatabase.CompletionDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -941,6 +941,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
//Complete Mission\Quests
|
//Complete Mission\Quests
|
||||||
Missions: [Schema.Types.Mixed],
|
Missions: [Schema.Types.Mixed],
|
||||||
QuestKeys: [questKeysSchema],
|
QuestKeys: [questKeysSchema],
|
||||||
|
ActiveQuest: { type: String, default: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain" }, //TODO: check after mission starting gear
|
||||||
//item like DojoKey or Boss missions key
|
//item like DojoKey or Boss missions key
|
||||||
LevelKeys: [Schema.Types.Mixed],
|
LevelKeys: [Schema.Types.Mixed],
|
||||||
//Active quests
|
//Active quests
|
||||||
@ -1164,7 +1165,7 @@ inventorySchema.set("toJSON", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// type overwrites for subdocuments/subdocument arrays
|
// type overwrites for subdocuments/subdocument arrays
|
||||||
type InventoryDocumentProps = {
|
export type InventoryDocumentProps = {
|
||||||
Suits: Types.DocumentArray<IEquipmentDatabase>;
|
Suits: Types.DocumentArray<IEquipmentDatabase>;
|
||||||
LongGuns: Types.DocumentArray<IEquipmentDatabase>;
|
LongGuns: Types.DocumentArray<IEquipmentDatabase>;
|
||||||
Pistols: Types.DocumentArray<IEquipmentDatabase>;
|
Pistols: Types.DocumentArray<IEquipmentDatabase>;
|
||||||
|
@ -70,7 +70,7 @@ const apartmentSchema = new Schema<IApartment>(
|
|||||||
{
|
{
|
||||||
Rooms: [roomSchema],
|
Rooms: [roomSchema],
|
||||||
FavouriteLoadouts: [Schema.Types.Mixed],
|
FavouriteLoadouts: [Schema.Types.Mixed],
|
||||||
Gardening: gardeningSchema
|
Gardening: gardeningSchema // TODO: ensure this is correct
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
@ -96,7 +96,7 @@ const orbiterSchema = new Schema<IOrbiter>(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
const orbiterDefault: IOrbiter = {
|
const orbiterDefault: IOrbiter = {
|
||||||
Features: [],
|
Features: ["/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem"], //TODO: potentially remove after missionstarting gear
|
||||||
Rooms: [
|
Rooms: [
|
||||||
{ Name: "AlchemyRoom", MaxCapacity: 1600 },
|
{ Name: "AlchemyRoom", MaxCapacity: 1600 },
|
||||||
{ Name: "BridgeRoom", MaxCapacity: 1600 },
|
{ Name: "BridgeRoom", MaxCapacity: 1600 },
|
||||||
|
@ -78,6 +78,9 @@ import { updateChallengeProgressController } from "@/src/controllers/api/updateC
|
|||||||
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 "../controllers/api/updateThemeController";
|
||||||
import { upgradesController } from "@/src/controllers/api/upgradesController";
|
import { upgradesController } from "@/src/controllers/api/upgradesController";
|
||||||
|
import { updateQuestController } from "@/src/controllers/api/updateQuestController";
|
||||||
|
import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
|
||||||
|
import { unlockShipFeatureController } from "@/src/controllers/api/unlockShipFeatureController";
|
||||||
|
|
||||||
const apiRouter = express.Router();
|
const apiRouter = express.Router();
|
||||||
|
|
||||||
@ -132,6 +135,7 @@ apiRouter.post("/genericUpdate.php", genericUpdateController);
|
|||||||
apiRouter.post("/getAlliance.php", getAllianceController);
|
apiRouter.post("/getAlliance.php", getAllianceController);
|
||||||
apiRouter.post("/getVoidProjectionRewards.php", getVoidProjectionRewardsController);
|
apiRouter.post("/getVoidProjectionRewards.php", getVoidProjectionRewardsController);
|
||||||
apiRouter.post("/gildWeapon.php", gildWeaponController);
|
apiRouter.post("/gildWeapon.php", gildWeaponController);
|
||||||
|
apiRouter.post("/giveKeyChainTriggeredItems.php", giveKeyChainTriggeredItemsController);
|
||||||
apiRouter.post("/guildTech.php", guildTechController);
|
apiRouter.post("/guildTech.php", guildTechController);
|
||||||
apiRouter.post("/hostSession.php", hostSessionController);
|
apiRouter.post("/hostSession.php", hostSessionController);
|
||||||
apiRouter.post("/infestedFoundry.php", infestedFoundryController);
|
apiRouter.post("/infestedFoundry.php", infestedFoundryController);
|
||||||
@ -161,10 +165,12 @@ apiRouter.post("/syndicateSacrifice.php", syndicateSacrificeController);
|
|||||||
apiRouter.post("/syndicateStandingBonus.php", syndicateStandingBonusController);
|
apiRouter.post("/syndicateStandingBonus.php", syndicateStandingBonusController);
|
||||||
apiRouter.post("/tauntHistory.php", tauntHistoryController);
|
apiRouter.post("/tauntHistory.php", tauntHistoryController);
|
||||||
apiRouter.post("/trainingResult.php", trainingResultController);
|
apiRouter.post("/trainingResult.php", trainingResultController);
|
||||||
|
apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController);
|
||||||
apiRouter.post("/updateChallengeProgress.php", updateChallengeProgressController);
|
apiRouter.post("/updateChallengeProgress.php", updateChallengeProgressController);
|
||||||
apiRouter.post("/updateNodeIntros.php", genericUpdateController);
|
apiRouter.post("/updateNodeIntros.php", genericUpdateController);
|
||||||
apiRouter.post("/updateSession.php", updateSessionPostController);
|
apiRouter.post("/updateSession.php", updateSessionPostController);
|
||||||
apiRouter.post("/updateTheme.php", updateThemeController);
|
apiRouter.post("/updateTheme.php", updateThemeController);
|
||||||
|
apiRouter.post("/updateQuest.php", updateQuestController);
|
||||||
apiRouter.post("/upgrades.php", upgradesController);
|
apiRouter.post("/upgrades.php", upgradesController);
|
||||||
|
|
||||||
export { apiRouter };
|
export { apiRouter };
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import {
|
||||||
|
Inventory,
|
||||||
|
InventoryDocumentProps,
|
||||||
|
TInventoryDatabaseDocument
|
||||||
|
} from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { Types } from "mongoose";
|
import { HydratedDocument, Types } from "mongoose";
|
||||||
import { SlotNames, IInventoryChanges, IBinChanges, ICurrencyChanges } from "@/src/types/purchaseTypes";
|
import { SlotNames, IInventoryChanges, IBinChanges, ICurrencyChanges } from "@/src/types/purchaseTypes";
|
||||||
import {
|
import {
|
||||||
IChallengeProgress,
|
IChallengeProgress,
|
||||||
@ -14,9 +18,9 @@ import {
|
|||||||
InventorySlot,
|
InventorySlot,
|
||||||
IWeaponSkinClient,
|
IWeaponSkinClient,
|
||||||
TEquipmentKey,
|
TEquipmentKey,
|
||||||
equipmentKeys,
|
|
||||||
IFusionTreasure,
|
IFusionTreasure,
|
||||||
IDailyAffiliations
|
IDailyAffiliations,
|
||||||
|
IInventoryDatabase
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IGenericUpdate } from "../types/genericUpdate";
|
import { IGenericUpdate } from "../types/genericUpdate";
|
||||||
import {
|
import {
|
||||||
@ -25,7 +29,7 @@ import {
|
|||||||
IUpdateChallengeProgressRequest
|
IUpdateChallengeProgressRequest
|
||||||
} from "../types/requestTypes";
|
} from "../types/requestTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { getWeaponType, getExalted } from "@/src/services/itemDataService";
|
import { getWeaponType, getExalted, getKeyChainItems } from "@/src/services/itemDataService";
|
||||||
import { IEquipmentClient, IItemConfig } from "../types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient, IItemConfig } from "../types/inventoryTypes/commonInventoryTypes";
|
||||||
import {
|
import {
|
||||||
ExportArcanes,
|
ExportArcanes,
|
||||||
@ -39,6 +43,8 @@ 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 { IGiveKeyChainTriggeredItemsRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
|
||||||
|
|
||||||
export const createInventory = async (
|
export const createInventory = async (
|
||||||
accountOwnerId: Types.ObjectId,
|
accountOwnerId: Types.ObjectId,
|
||||||
@ -64,31 +70,32 @@ export const createInventory = async (
|
|||||||
{ ItemCount: 1, ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2" },
|
{ ItemCount: 1, ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2" },
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3" },
|
{ ItemCount: 1, ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3" },
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4" },
|
{ ItemCount: 1, ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4" },
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Types/Restoratives/LisetAutoHack" },
|
{ ItemCount: 1, ItemType: "/Lotus/Types/Restoratives/LisetAutoHack" }
|
||||||
|
|
||||||
// Vor's Prize rewards
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarHealthMaxMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityStrengthMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityDurationMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarPickupBonusMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarPowerMaxMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Melee/WeaponFireRateMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Melee/WeaponMeleeDamageMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponFactionDamageCorpus" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponFactionDamageGrineer" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponDamageAmountMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponFireDamageMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponElectricityDamageMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponDamageAmountMod" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Types/Recipes/Weapons/BurstonRifleBlueprint" },
|
|
||||||
{ ItemCount: 1, ItemType: "/Lotus/Types/Items/MiscItems/Morphic" },
|
|
||||||
{ ItemCount: 400, ItemType: "/Lotus/Types/Items/MiscItems/PolymerBundle" },
|
|
||||||
{ ItemCount: 150, ItemType: "/Lotus/Types/Items/MiscItems/AlloyPlate" }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// const vorsPrizeRewards = [
|
||||||
|
// // Vor's Prize rewards
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarHealthMaxMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityStrengthMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityDurationMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarPickupBonusMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarPowerMaxMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Melee/WeaponFireRateMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Melee/WeaponMeleeDamageMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponFactionDamageCorpus" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponFactionDamageGrineer" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponDamageAmountMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponFireDamageMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponElectricityDamageMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponDamageAmountMod" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Types/Recipes/Weapons/BurstonRifleBlueprint" },
|
||||||
|
// { ItemCount: 1, ItemType: "/Lotus/Types/Items/MiscItems/Morphic" },
|
||||||
|
// { ItemCount: 400, ItemType: "/Lotus/Types/Items/MiscItems/PolymerBundle" },
|
||||||
|
// { ItemCount: 150, ItemType: "/Lotus/Types/Items/MiscItems/AlloyPlate" }
|
||||||
|
// ];
|
||||||
for (const equipment of defaultEquipment) {
|
for (const equipment of defaultEquipment) {
|
||||||
await addItem(inventory, equipment.ItemType, equipment.ItemCount);
|
await addItem(inventory, equipment.ItemType, equipment.ItemCount);
|
||||||
}
|
}
|
||||||
@ -109,7 +116,6 @@ export const createInventory = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
inventory.QuestKeys.push({
|
inventory.QuestKeys.push({
|
||||||
Completed: true,
|
|
||||||
ItemType: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain"
|
ItemType: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain"
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -132,13 +138,19 @@ export const createInventory = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines two inventory changes objects into one.
|
||||||
|
*
|
||||||
|
* @param InventoryChanges - will hold the combined changes
|
||||||
|
* @param delta - inventory changes to be added
|
||||||
|
*/
|
||||||
export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, delta: IInventoryChanges): void => {
|
export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, delta: IInventoryChanges): void => {
|
||||||
for (const key in delta) {
|
for (const key in delta) {
|
||||||
if (!(key in InventoryChanges)) {
|
if (!(key in InventoryChanges)) {
|
||||||
InventoryChanges[key] = delta[key];
|
InventoryChanges[key] = delta[key];
|
||||||
} else if (Array.isArray(delta[key])) {
|
} else if (Array.isArray(delta[key])) {
|
||||||
const left = InventoryChanges[key] as object[];
|
const left = InventoryChanges[key] as object[];
|
||||||
const right: object[] = delta[key];
|
const right: object[] | string[] = delta[key];
|
||||||
for (const item of right) {
|
for (const item of right) {
|
||||||
left.push(item);
|
left.push(item);
|
||||||
}
|
}
|
||||||
@ -154,8 +166,10 @@ export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, del
|
|||||||
left.Extra ??= 0;
|
left.Extra ??= 0;
|
||||||
left.Extra += right.Extra;
|
left.Extra += right.Extra;
|
||||||
}
|
}
|
||||||
|
} else if (typeof delta[key] === "number") {
|
||||||
|
(InventoryChanges[key] as number) += delta[key];
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`inventory change not merged: ${key}`);
|
throw new Error(`inventory change not merged: unhandled type for inventory key ${key}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -274,6 +288,24 @@ export const addItem = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (typeName in creditBundles) {
|
||||||
|
const creditsTotal = creditBundles[typeName] * quantity;
|
||||||
|
inventory.RegularCredits += creditsTotal;
|
||||||
|
return {
|
||||||
|
InventoryChanges: {
|
||||||
|
RegularCredits: creditsTotal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (typeName in fusionBundles) {
|
||||||
|
const fusionPointsTotal = fusionBundles[typeName] * quantity;
|
||||||
|
inventory.FusionPoints += fusionPointsTotal;
|
||||||
|
return {
|
||||||
|
InventoryChanges: {
|
||||||
|
FusionPoints: fusionPointsTotal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Path-based duck typing
|
// Path-based duck typing
|
||||||
switch (typeName.substr(1).split("/")[1]) {
|
switch (typeName.substr(1).split("/")[1]) {
|
||||||
@ -364,7 +396,7 @@ export const addItem = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "Game":
|
case "Game": {
|
||||||
if (typeName.substr(1).split("/")[3] == "Projections") {
|
if (typeName.substr(1).split("/")[3] == "Projections") {
|
||||||
// Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze
|
// Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze
|
||||||
const miscItemChanges = [
|
const miscItemChanges = [
|
||||||
@ -382,6 +414,40 @@ export const addItem = async (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "Keys": {
|
||||||
|
inventory.QuestKeys.push({ ItemType: typeName });
|
||||||
|
return {
|
||||||
|
InventoryChanges: {
|
||||||
|
QuestKeys: [
|
||||||
|
{
|
||||||
|
ItemType: typeName
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "NeutralCreatures": {
|
||||||
|
const horseIndex = inventory.Horses.push({ ItemType: typeName });
|
||||||
|
return {
|
||||||
|
InventoryChanges: {
|
||||||
|
Horses: inventory.Horses[horseIndex - 1].toJSON()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "Recipes": {
|
||||||
|
inventory.MiscItems.push({ ItemType: typeName, ItemCount: quantity });
|
||||||
|
return {
|
||||||
|
InventoryChanges: {
|
||||||
|
MiscItems: [
|
||||||
|
{
|
||||||
|
ItemType: typeName,
|
||||||
|
ItemCount: quantity
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const errorMessage = `unable to add item: ${typeName}`;
|
const errorMessage = `unable to add item: ${typeName}`;
|
||||||
@ -684,7 +750,8 @@ const addCrewShip = (
|
|||||||
return inventoryChanges;
|
return inventoryChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addGearExpByCategory = (
|
//TODO: wrong id is not erroring
|
||||||
|
export const addGearExpByCategory = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
gearArray: IEquipmentClient[] | undefined,
|
gearArray: IEquipmentClient[] | undefined,
|
||||||
categoryName: TEquipmentKey
|
categoryName: TEquipmentKey
|
||||||
@ -870,7 +937,7 @@ export const addChallenges = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes }: IMission): void => {
|
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes }: IMission): void => {
|
||||||
const { Missions } = inventory;
|
const { Missions } = inventory;
|
||||||
const itemIndex = Missions.findIndex(item => item.Tag === Tag);
|
const itemIndex = Missions.findIndex(item => item.Tag === Tag);
|
||||||
|
|
||||||
@ -882,83 +949,6 @@ const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Comple
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const missionInventoryUpdate = async (data: IMissionInventoryUpdateRequest, accountId: string) => {
|
|
||||||
const {
|
|
||||||
RawUpgrades,
|
|
||||||
MiscItems,
|
|
||||||
RegularCredits,
|
|
||||||
ChallengeProgress,
|
|
||||||
FusionPoints,
|
|
||||||
Consumables,
|
|
||||||
Recipes,
|
|
||||||
Missions,
|
|
||||||
FusionTreasures
|
|
||||||
} = data;
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
|
|
||||||
// credits
|
|
||||||
inventory.RegularCredits += RegularCredits || 0;
|
|
||||||
|
|
||||||
// endo
|
|
||||||
inventory.FusionPoints += FusionPoints || 0;
|
|
||||||
|
|
||||||
// syndicate
|
|
||||||
data.AffiliationChanges?.forEach(affiliation => {
|
|
||||||
const syndicate = inventory.Affiliations.find(x => x.Tag == affiliation.Tag);
|
|
||||||
if (syndicate !== undefined) {
|
|
||||||
syndicate.Standing =
|
|
||||||
syndicate.Standing === undefined ? affiliation.Standing : syndicate.Standing + affiliation.Standing;
|
|
||||||
syndicate.Title = syndicate.Title === undefined ? affiliation.Title : syndicate.Title + affiliation.Title;
|
|
||||||
} else {
|
|
||||||
inventory.Affiliations.push({
|
|
||||||
Standing: affiliation.Standing,
|
|
||||||
Title: affiliation.Title,
|
|
||||||
Tag: affiliation.Tag,
|
|
||||||
FreeFavorsEarned: [],
|
|
||||||
FreeFavorsUsed: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Gear XP
|
|
||||||
equipmentKeys.forEach(key => addGearExpByCategory(inventory, data[key], key));
|
|
||||||
|
|
||||||
// Incarnon Challenges
|
|
||||||
if (data.EvolutionProgress) {
|
|
||||||
for (const evoProgress of data.EvolutionProgress) {
|
|
||||||
const entry = inventory.EvolutionProgress
|
|
||||||
? inventory.EvolutionProgress.find(entry => entry.ItemType == evoProgress.ItemType)
|
|
||||||
: undefined;
|
|
||||||
if (entry) {
|
|
||||||
entry.Progress = evoProgress.Progress;
|
|
||||||
entry.Rank = evoProgress.Rank;
|
|
||||||
} else {
|
|
||||||
inventory.EvolutionProgress ??= [];
|
|
||||||
inventory.EvolutionProgress.push(evoProgress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastRegionPlayed
|
|
||||||
if (data.LastRegionPlayed) {
|
|
||||||
inventory.LastRegionPlayed = data.LastRegionPlayed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// other
|
|
||||||
addMods(inventory, RawUpgrades);
|
|
||||||
addMiscItems(inventory, MiscItems);
|
|
||||||
addConsumables(inventory, Consumables);
|
|
||||||
addRecipes(inventory, Recipes);
|
|
||||||
addChallenges(inventory, ChallengeProgress);
|
|
||||||
addFusionTreasures(inventory, FusionTreasures);
|
|
||||||
if (Missions) {
|
|
||||||
addMissionComplete(inventory, Missions);
|
|
||||||
}
|
|
||||||
|
|
||||||
const changedInventory = await inventory.save();
|
|
||||||
return changedInventory.toJSON();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addBooster = async (ItemType: string, time: number, accountId: string): Promise<void> => {
|
export const addBooster = async (ItemType: string, time: number, accountId: string): Promise<void> => {
|
||||||
const currentTime = Math.floor(Date.now() / 1000) - 129600; // Value is wrong without 129600. Figure out why, please. :)
|
const currentTime = Math.floor(Date.now() / 1000) - 129600; // Value is wrong without 129600. Figure out why, please. :)
|
||||||
|
|
||||||
@ -977,3 +967,52 @@ export const addBooster = async (ItemType: string, time: number, accountId: stri
|
|||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateSyndicate = (
|
||||||
|
inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
|
||||||
|
syndicateUpdate: IMissionInventoryUpdateRequest["AffiliationChanges"]
|
||||||
|
) => {
|
||||||
|
syndicateUpdate?.forEach(affiliation => {
|
||||||
|
const syndicate = inventory.Affiliations.find(x => x.Tag == affiliation.Tag);
|
||||||
|
if (syndicate !== undefined) {
|
||||||
|
syndicate.Standing =
|
||||||
|
syndicate.Standing === undefined ? affiliation.Standing : syndicate.Standing + affiliation.Standing;
|
||||||
|
syndicate.Title = syndicate.Title === undefined ? affiliation.Title : syndicate.Title + affiliation.Title;
|
||||||
|
} else {
|
||||||
|
inventory.Affiliations.push({
|
||||||
|
Standing: affiliation.Standing,
|
||||||
|
Title: affiliation.Title,
|
||||||
|
Tag: affiliation.Tag,
|
||||||
|
FreeFavorsEarned: [],
|
||||||
|
FreeFavorsUsed: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { AffiliationMods: [] };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns object with inventory keys of changes or empty object when no items were added
|
||||||
|
*/
|
||||||
|
export const addKeyChainItems = async (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
keyChainData: IGiveKeyChainTriggeredItemsRequest
|
||||||
|
): Promise<IInventoryChanges> => {
|
||||||
|
const keyChainItems = getKeyChainItems(keyChainData);
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`adding key chain items ${keyChainItems.join()} for ${keyChainData.KeyChain} at stage ${keyChainData.ChainStage}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const nonStoreItems = keyChainItems.map(item => item.replace("StoreItems/", ""));
|
||||||
|
|
||||||
|
//TODO: inventoryChanges is not typed correctly
|
||||||
|
const inventoryChanges = {};
|
||||||
|
|
||||||
|
for (const item of nonStoreItems) {
|
||||||
|
const inventoryChangesDelta = await addItem(inventory, item);
|
||||||
|
combineInventoryChanges(inventoryChanges, inventoryChangesDelta.InventoryChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
return inventoryChanges;
|
||||||
|
};
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import { IGiveKeyChainTriggeredItemsRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
|
||||||
import { getIndexAfter } from "@/src/helpers/stringHelpers";
|
import { getIndexAfter } from "@/src/helpers/stringHelpers";
|
||||||
|
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
import {
|
import {
|
||||||
dict_de,
|
dict_de,
|
||||||
dict_en,
|
dict_en,
|
||||||
@ -20,13 +23,16 @@ import {
|
|||||||
ExportGear,
|
ExportGear,
|
||||||
ExportKeys,
|
ExportKeys,
|
||||||
ExportRecipes,
|
ExportRecipes,
|
||||||
|
ExportRegions,
|
||||||
ExportResources,
|
ExportResources,
|
||||||
ExportSentinels,
|
ExportSentinels,
|
||||||
ExportWarframes,
|
ExportWarframes,
|
||||||
ExportWeapons,
|
ExportWeapons,
|
||||||
IPowersuit,
|
IPowersuit,
|
||||||
IRecipe
|
IRecipe,
|
||||||
|
IRegion
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
|
import questCompletionItems from "@/static/fixed_responses/questCompletionRewards.json";
|
||||||
|
|
||||||
export type WeaponTypeInternal =
|
export type WeaponTypeInternal =
|
||||||
| "LongGuns"
|
| "LongGuns"
|
||||||
@ -150,3 +156,56 @@ export const getDict = (lang: string): Record<string, string> => {
|
|||||||
export const getString = (key: string, dict: Record<string, string>): string => {
|
export const getString = (key: string, dict: Record<string, string>): string => {
|
||||||
return dict[key] ?? key;
|
return dict[key] ?? key;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getKeyChainItems = ({ KeyChain, ChainStage }: IGiveKeyChainTriggeredItemsRequest): string[] => {
|
||||||
|
const chainStages = ExportKeys[KeyChain].chainStages;
|
||||||
|
if (!chainStages) {
|
||||||
|
throw new Error(`KeyChain ${KeyChain} does not contain chain stages`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyChainStage = chainStages[ChainStage];
|
||||||
|
if (!keyChainStage) {
|
||||||
|
throw new Error(`KeyChainStage ${ChainStage} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyChainStage.itemsToGiveWhenTriggered.length === 0) {
|
||||||
|
throw new Error(`No items to give for KeyChain ${KeyChain} at stage ${ChainStage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyChainStage.itemsToGiveWhenTriggered;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLevelKeyRewards = (levelKey: string) => {
|
||||||
|
const levelKeyData = ExportKeys[levelKey];
|
||||||
|
if (!levelKeyData) {
|
||||||
|
const error = `LevelKey ${levelKey} not found`;
|
||||||
|
logger.error(error);
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!levelKeyData.rewards) {
|
||||||
|
const error = `LevelKey ${levelKey} does not contain rewards`;
|
||||||
|
logger.error(error);
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return levelKeyData.rewards;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNode = (nodeName: string): IRegion => {
|
||||||
|
const node = ExportRegions[nodeName];
|
||||||
|
if (!node) {
|
||||||
|
throw new Error(`Node ${nodeName} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getQuestCompletionItems = (questKey: string) => {
|
||||||
|
const items = (questCompletionItems as unknown as Record<string, ITypeCount[] | undefined>)[questKey];
|
||||||
|
if (!items) {
|
||||||
|
throw new Error(`Quest ${questKey} not found in questCompletionItems`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
@ -45,16 +45,16 @@ export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Typ
|
|||||||
activeShipId: shipId
|
activeShipId: shipId
|
||||||
});
|
});
|
||||||
if (config.skipTutorial) {
|
if (config.skipTutorial) {
|
||||||
// Vor's Prize rewards
|
// // Vor's Prize rewards
|
||||||
const defaultFeatures = [
|
// const defaultFeatures = [
|
||||||
"/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem",
|
// "/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();
|
||||||
};
|
};
|
||||||
|
@ -1,30 +1,280 @@
|
|||||||
import { IMissionRewardResponse, IInventoryFieldType, inventoryFields } from "@/src/types/missionTypes";
|
import { ExportRegions, ExportRewards, IReward } from "warframe-public-export-plus";
|
||||||
|
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
||||||
import {
|
|
||||||
ExportRegions,
|
|
||||||
ExportRewards,
|
|
||||||
ExportUpgrades,
|
|
||||||
ExportGear,
|
|
||||||
ExportRecipes,
|
|
||||||
ExportRelics,
|
|
||||||
ExportResources,
|
|
||||||
IReward
|
|
||||||
} from "warframe-public-export-plus";
|
|
||||||
import { IMissionInventoryUpdateRequest } 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";
|
||||||
|
import { IInventoryDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import {
|
||||||
|
addChallenges,
|
||||||
|
addConsumables,
|
||||||
|
addFusionTreasures,
|
||||||
|
addGearExpByCategory,
|
||||||
|
addItem,
|
||||||
|
addMiscItems,
|
||||||
|
addMissionComplete,
|
||||||
|
addMods,
|
||||||
|
addRecipes,
|
||||||
|
combineInventoryChanges,
|
||||||
|
updateSyndicate
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { updateQuestKey } from "@/src/services/questService";
|
||||||
|
import { HydratedDocument } from "mongoose";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { getLevelKeyRewards, getNode } from "@/src/services/itemDataService";
|
||||||
|
import { InventoryDocumentProps, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
// need reverse engineer rewardSeed, otherwise ingame displayed rotation reward will be different than added to db or displayed on mission end
|
const getRotations = (rotationCount: number): number[] => {
|
||||||
const getRewards = ({
|
if (rotationCount === 0) return [0];
|
||||||
RewardInfo
|
|
||||||
}: IMissionInventoryUpdateRequest): {
|
const rotationPattern = [0, 0, 1, 2]; // A, A, B, C
|
||||||
InventoryChanges: IMissionInventoryUpdateRequest;
|
const rotatedValues = [];
|
||||||
MissionRewards: IMissionRewardResponse[];
|
|
||||||
} => {
|
for (let i = 0; i < rotationCount; i++) {
|
||||||
if (!RewardInfo) {
|
rotatedValues.push(rotationPattern[i % rotationPattern.length]);
|
||||||
return { InventoryChanges: {}, MissionRewards: [] };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return rotatedValues;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
|
||||||
|
return getRandomReward(pool as IRngResult[]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const creditBundles: Record<string, number> = {
|
||||||
|
"/Lotus/Types/PickUps/Credits/1500Credits": 1500,
|
||||||
|
"/Lotus/Types/PickUps/Credits/2000Credits": 2000,
|
||||||
|
"/Lotus/Types/PickUps/Credits/2500Credits": 2500,
|
||||||
|
"/Lotus/Types/PickUps/Credits/3000Credits": 3000,
|
||||||
|
"/Lotus/Types/PickUps/Credits/4000Credits": 4000,
|
||||||
|
"/Lotus/Types/PickUps/Credits/5000Credits": 5000,
|
||||||
|
"/Lotus/Types/PickUps/Credits/7500Credits": 7500,
|
||||||
|
"/Lotus/Types/PickUps/Credits/10000Credits": 10000,
|
||||||
|
"/Lotus/Types/PickUps/Credits/5000Hollars": 5000,
|
||||||
|
"/Lotus/Types/PickUps/Credits/7500Hollars": 7500,
|
||||||
|
"/Lotus/Types/PickUps/Credits/10000Hollars": 10000,
|
||||||
|
"/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardOneHard": 105000,
|
||||||
|
"/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardTwoHard": 175000,
|
||||||
|
"/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardThreeHard": 250000,
|
||||||
|
"/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsCommon": 15000,
|
||||||
|
"/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsUncommon": 30000
|
||||||
|
};
|
||||||
|
|
||||||
|
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 Entries<T, K extends keyof T = keyof T> = (K extends unknown ? [K, T[K]] : never)[];
|
||||||
|
|
||||||
|
function getEntriesUnsafe<T extends object>(object: T): Entries<T> {
|
||||||
|
return Object.entries(object) as Entries<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
//type TMissionInventoryUpdateKeys = keyof IMissionInventoryUpdateRequest;
|
||||||
|
//const ignoredInventoryUpdateKeys = ["FpsAvg", "FpsMax", "FpsMin", "FpsSamples"] satisfies TMissionInventoryUpdateKeys[]; // for keys with no meaning for this server
|
||||||
|
//type TignoredInventoryUpdateKeys = (typeof ignoredInventoryUpdateKeys)[number];
|
||||||
|
//const knownUnhandledKeys: readonly string[] = ["test"] as const; // for unimplemented but important keys
|
||||||
|
|
||||||
|
export const addMissionInventoryUpdates = (
|
||||||
|
inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
|
||||||
|
inventoryUpdates: IMissionInventoryUpdateRequest
|
||||||
|
) => {
|
||||||
|
//TODO: type this properly
|
||||||
|
const inventoryChanges: Partial<IInventoryDatabase> = {};
|
||||||
|
if (inventoryUpdates.MissionFailed === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) {
|
||||||
|
if (value === undefined) {
|
||||||
|
logger.error(`Inventory update key ${key} has no value `);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (key) {
|
||||||
|
case "RegularCredits":
|
||||||
|
inventory.RegularCredits += value;
|
||||||
|
break;
|
||||||
|
case "QuestKeys":
|
||||||
|
updateQuestKey(inventory, value);
|
||||||
|
break;
|
||||||
|
case "AffiliationChanges":
|
||||||
|
updateSyndicate(inventory, value);
|
||||||
|
break;
|
||||||
|
// Incarnon Challenges
|
||||||
|
case "EvolutionProgress": {
|
||||||
|
for (const evoProgress of value) {
|
||||||
|
const entry = inventory.EvolutionProgress
|
||||||
|
? inventory.EvolutionProgress.find(entry => entry.ItemType == evoProgress.ItemType)
|
||||||
|
: undefined;
|
||||||
|
if (entry) {
|
||||||
|
entry.Progress = evoProgress.Progress;
|
||||||
|
entry.Rank = evoProgress.Rank;
|
||||||
|
} else {
|
||||||
|
inventory.EvolutionProgress ??= [];
|
||||||
|
inventory.EvolutionProgress.push(evoProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Missions":
|
||||||
|
addMissionComplete(inventory, value);
|
||||||
|
break;
|
||||||
|
case "LastRegionPlayed":
|
||||||
|
inventory.LastRegionPlayed = value;
|
||||||
|
break;
|
||||||
|
case "RawUpgrades":
|
||||||
|
addMods(inventory, value);
|
||||||
|
break;
|
||||||
|
case "MiscItems":
|
||||||
|
addMiscItems(inventory, value);
|
||||||
|
break;
|
||||||
|
case "Consumables":
|
||||||
|
addConsumables(inventory, value);
|
||||||
|
break;
|
||||||
|
case "Recipes":
|
||||||
|
addRecipes(inventory, value);
|
||||||
|
break;
|
||||||
|
case "ChallengeProgress":
|
||||||
|
addChallenges(inventory, value);
|
||||||
|
break;
|
||||||
|
case "FusionTreasures":
|
||||||
|
addFusionTreasures(inventory, value);
|
||||||
|
break;
|
||||||
|
case "FusionBundles": {
|
||||||
|
let fusionPoints = 0;
|
||||||
|
for (const fusionBundle of value) {
|
||||||
|
const fusionPointsTotal = fusionBundles[fusionBundle.ItemType] * fusionBundle.ItemCount;
|
||||||
|
inventory.FusionPoints += fusionPointsTotal;
|
||||||
|
fusionPoints += fusionPointsTotal;
|
||||||
|
}
|
||||||
|
inventoryChanges.FusionPoints = fusionPoints;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Equipment XP updates
|
||||||
|
case "Suits":
|
||||||
|
case "LongGuns":
|
||||||
|
case "Pistols":
|
||||||
|
case "Melee":
|
||||||
|
case "SpecialItems":
|
||||||
|
case "Sentinels":
|
||||||
|
case "SentinelWeapons":
|
||||||
|
case "SpaceSuits":
|
||||||
|
case "SpaceGuns":
|
||||||
|
case "SpaceMelee":
|
||||||
|
case "Hoverboards":
|
||||||
|
case "OperatorAmps":
|
||||||
|
case "MoaPets":
|
||||||
|
addGearExpByCategory(inventory, value, key);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// if (
|
||||||
|
// (ignoredInventoryUpdateKeys as readonly string[]).includes(key) ||
|
||||||
|
// knownUnhandledKeys.includes(key)
|
||||||
|
// ) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// logger.error(`Unhandled inventory update key: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inventoryChanges;
|
||||||
|
};
|
||||||
|
|
||||||
|
//TODO: return type of partial missioninventoryupdate response
|
||||||
|
export const addMissionRewards = async (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
{ RewardInfo: rewardInfo, LevelKeyName: levelKeyName, Missions: missions }: IMissionInventoryUpdateRequest
|
||||||
|
) => {
|
||||||
|
if (!rewardInfo) {
|
||||||
|
logger.error("no reward info provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: check double reward merging
|
||||||
|
const MissionRewards = getRandomMissionDrops(rewardInfo).map(drop => {
|
||||||
|
return { StoreItem: drop.type, ItemCount: drop.itemCount };
|
||||||
|
});
|
||||||
|
console.log("random mission drops:", MissionRewards);
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
|
||||||
|
let missionCompletionCredits = 0;
|
||||||
|
//inventory change is what the client has not rewarded itself, credit updates seem to be taken from totalCredits
|
||||||
|
if (levelKeyName) {
|
||||||
|
const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
|
||||||
|
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
|
||||||
|
for (const reward of fixedLevelRewards) {
|
||||||
|
if (reward.rewardType == "RT_CREDITS") {
|
||||||
|
inventory.RegularCredits += reward.amount;
|
||||||
|
missionCompletionCredits += reward.amount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MissionRewards.push({
|
||||||
|
StoreItem: reward.itemType,
|
||||||
|
ItemCount: reward.rewardType === "RT_RESOURCE" ? reward.amount : 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missions) {
|
||||||
|
const levelCreditReward = getLevelCreditRewards(missions?.Tag);
|
||||||
|
missionCompletionCredits += levelCreditReward;
|
||||||
|
inventory.RegularCredits += levelCreditReward;
|
||||||
|
logger.debug(`levelCreditReward ${levelCreditReward}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: resolve issue with creditbundles
|
||||||
|
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
|
||||||
|
//TODO: some conditional types to rule out binchanges?
|
||||||
|
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { inventoryChanges, MissionRewards, missionCompletionCredits };
|
||||||
|
};
|
||||||
|
|
||||||
|
//might not be faithful to original
|
||||||
|
//TODO: consider ActiveBoosters
|
||||||
|
export const calculateFinalCredits = (
|
||||||
|
inventory: HydratedDocument<IInventoryDatabase>,
|
||||||
|
{
|
||||||
|
missionDropCredits,
|
||||||
|
missionCompletionCredits,
|
||||||
|
rngRewardCredits = 0
|
||||||
|
}: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number }
|
||||||
|
) => {
|
||||||
|
const hasDailyCreditBonus = true;
|
||||||
|
const totalCredits = missionDropCredits + missionCompletionCredits + rngRewardCredits;
|
||||||
|
|
||||||
|
const finalCredits = {
|
||||||
|
MissionCredits: [missionDropCredits, missionDropCredits],
|
||||||
|
CreditBonus: [missionCompletionCredits, missionCompletionCredits],
|
||||||
|
TotalCredits: [totalCredits, totalCredits]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasDailyCreditBonus) {
|
||||||
|
inventory.RegularCredits += totalCredits;
|
||||||
|
finalCredits.CreditBonus[1] *= 2;
|
||||||
|
finalCredits.MissionCredits[1] *= 2;
|
||||||
|
finalCredits.TotalCredits[1] *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasDailyCreditBonus) {
|
||||||
|
return finalCredits;
|
||||||
|
}
|
||||||
|
return { ...finalCredits, DailyMissionBonus: true };
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLevelCreditRewards(nodeName: string): number {
|
||||||
|
const minEnemyLevel = getNode(nodeName).minEnemyLevel;
|
||||||
|
|
||||||
|
return 1000 + (minEnemyLevel - 1) * 100;
|
||||||
|
|
||||||
|
//TODO: get dark sektor fixed credit rewards and railjack bonus
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomMissionDrops(RewardInfo: IRewardInfo): IRngResult[] {
|
||||||
const drops: IRngResult[] = [];
|
const drops: IRngResult[] = [];
|
||||||
if (RewardInfo.node in ExportRegions) {
|
if (RewardInfo.node in ExportRegions) {
|
||||||
const region = ExportRegions[RewardInfo.node];
|
const region = ExportRegions[RewardInfo.node];
|
||||||
@ -53,161 +303,16 @@ const getRewards = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) {
|
if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) {
|
||||||
|
console.log("cache rewards", RewardInfo.EnemyCachesFound);
|
||||||
const deck = ExportRewards[region.cacheRewardManifest];
|
const deck = ExportRewards[region.cacheRewardManifest];
|
||||||
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
|
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
|
||||||
const drop = getRandomRewardByChance(deck[rotation]);
|
const drop = getRandomRewardByChance(deck[rotation]);
|
||||||
if (drop) {
|
if (drop) {
|
||||||
|
console.log("cache drop", drop);
|
||||||
drops.push(drop);
|
drops.push(drop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return drops;
|
||||||
logger.debug("Mission rewards:", drops);
|
}
|
||||||
return formatRewardsToInventoryType(drops);
|
|
||||||
};
|
|
||||||
|
|
||||||
const combineRewardAndLootInventory = (
|
|
||||||
rewardInventory: IMissionInventoryUpdateRequest,
|
|
||||||
lootInventory: IMissionInventoryUpdateRequest
|
|
||||||
) => {
|
|
||||||
const missionCredits = lootInventory.RegularCredits || 0;
|
|
||||||
const creditsBonus = rewardInventory.RegularCredits || 0;
|
|
||||||
const totalCredits = missionCredits + creditsBonus;
|
|
||||||
let FusionPoints = rewardInventory.FusionPoints || 0;
|
|
||||||
|
|
||||||
// Discharge Endo picked up during the mission
|
|
||||||
if (lootInventory.FusionBundles) {
|
|
||||||
for (const fusionBundle of lootInventory.FusionBundles) {
|
|
||||||
if (fusionBundle.ItemType in fusionBundles) {
|
|
||||||
FusionPoints += fusionBundles[fusionBundle.ItemType] * fusionBundle.ItemCount;
|
|
||||||
} else {
|
|
||||||
logger.error(`unknown fusion bundle: ${fusionBundle.ItemType}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lootInventory.FusionBundles = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
lootInventory.RegularCredits = totalCredits;
|
|
||||||
lootInventory.FusionPoints = FusionPoints;
|
|
||||||
inventoryFields.forEach((field: IInventoryFieldType) => {
|
|
||||||
if (rewardInventory[field] && !lootInventory[field]) {
|
|
||||||
lootInventory[field] = [];
|
|
||||||
}
|
|
||||||
rewardInventory[field]?.forEach(item => lootInventory[field]!.push(item));
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
combinedInventoryChanges: lootInventory,
|
|
||||||
TotalCredits: [totalCredits, totalCredits],
|
|
||||||
CreditsBonus: [creditsBonus, creditsBonus],
|
|
||||||
MissionCredits: [missionCredits, missionCredits],
|
|
||||||
FusionPoints: FusionPoints
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRotations = (rotationCount: number): number[] => {
|
|
||||||
if (rotationCount === 0) return [0];
|
|
||||||
|
|
||||||
const rotationPattern = [0, 0, 1, 2]; // A, A, B, C
|
|
||||||
const rotatedValues = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < rotationCount; i++) {
|
|
||||||
rotatedValues.push(rotationPattern[i % rotationPattern.length]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rotatedValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
|
|
||||||
return getRandomReward(pool as IRngResult[]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const creditBundles: Record<string, number> = {
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/1500Credits": 1500,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/2000Credits": 2000,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/2500Credits": 2500,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/3000Credits": 3000,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/4000Credits": 4000,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/5000Credits": 5000,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/7500Credits": 7500,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/10000Credits": 10000,
|
|
||||||
"/Lotus/StoreItems/Types/StoreItems/CreditBundles/Zariman/TableACreditsCommon": 15000,
|
|
||||||
"/Lotus/StoreItems/Types/StoreItems/CreditBundles/Zariman/TableACreditsUncommon": 30000,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardOneHard": 105000,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardTwoHard": 175000,
|
|
||||||
"/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardThreeHard": 250000
|
|
||||||
};
|
|
||||||
|
|
||||||
const fusionBundles: Record<string, number> = {
|
|
||||||
"/Lotus/Upgrades/Mods/FusionBundles/CommonFusionBundle": 15,
|
|
||||||
"/Lotus/Upgrades/Mods/FusionBundles/UncommonFusionBundle": 50,
|
|
||||||
"/Lotus/Upgrades/Mods/FusionBundles/RareFusionBundle": 80
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatRewardsToInventoryType = (
|
|
||||||
rewards: IRngResult[]
|
|
||||||
): { InventoryChanges: IMissionInventoryUpdateRequest; MissionRewards: IMissionRewardResponse[] } => {
|
|
||||||
const InventoryChanges: IMissionInventoryUpdateRequest = {};
|
|
||||||
const MissionRewards: IMissionRewardResponse[] = [];
|
|
||||||
for (const reward of rewards) {
|
|
||||||
if (reward.type in creditBundles) {
|
|
||||||
InventoryChanges.RegularCredits ??= 0;
|
|
||||||
InventoryChanges.RegularCredits += creditBundles[reward.type] * reward.itemCount;
|
|
||||||
} else {
|
|
||||||
const type = reward.type.replace("/Lotus/StoreItems/", "/Lotus/");
|
|
||||||
if (type in fusionBundles) {
|
|
||||||
InventoryChanges.FusionPoints ??= 0;
|
|
||||||
InventoryChanges.FusionPoints += fusionBundles[type] * reward.itemCount;
|
|
||||||
} else if (type in ExportUpgrades) {
|
|
||||||
addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "RawUpgrades");
|
|
||||||
} else if (type in ExportGear) {
|
|
||||||
addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "Consumables");
|
|
||||||
} else if (type in ExportRecipes) {
|
|
||||||
addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "Recipes");
|
|
||||||
} else if (
|
|
||||||
type in ExportRelics ||
|
|
||||||
(type in ExportResources && ExportResources[type].productCategory == "MiscItems")
|
|
||||||
) {
|
|
||||||
addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "MiscItems");
|
|
||||||
} else {
|
|
||||||
logger.error(`rolled reward ${reward.itemCount}X ${reward.type} but unsure how to give it`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { InventoryChanges, MissionRewards };
|
|
||||||
};
|
|
||||||
|
|
||||||
const addRewardResponse = (
|
|
||||||
InventoryChanges: IMissionInventoryUpdateRequest,
|
|
||||||
MissionRewards: IMissionRewardResponse[],
|
|
||||||
ItemType: string,
|
|
||||||
ItemCount: number,
|
|
||||||
InventoryCategory: IInventoryFieldType
|
|
||||||
) => {
|
|
||||||
if (!ItemType) return;
|
|
||||||
|
|
||||||
if (!InventoryChanges[InventoryCategory]) {
|
|
||||||
InventoryChanges[InventoryCategory] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const existReward = InventoryChanges[InventoryCategory].find(item => item.ItemType === ItemType);
|
|
||||||
if (existReward) {
|
|
||||||
existReward.ItemCount += ItemCount;
|
|
||||||
const missionReward = MissionRewards.find(missionReward => missionReward.TypeName === ItemType);
|
|
||||||
if (missionReward) {
|
|
||||||
missionReward.ItemCount += ItemCount;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
InventoryChanges[InventoryCategory].push({ ItemType, ItemCount });
|
|
||||||
MissionRewards.push({
|
|
||||||
ItemCount,
|
|
||||||
TweetText: ItemType, // ensure if/how this even still used, or if it's needed at all
|
|
||||||
ProductCategory: InventoryCategory,
|
|
||||||
StoreItem: ItemType.replace("/Lotus/", "/Lotus/StoreItems/"),
|
|
||||||
TypeName: ItemType
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { getRewards, combineRewardAndLootInventory };
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { PersonalRooms } from "@/src/models/personalRoomsModel";
|
import { PersonalRooms } from "@/src/models/personalRoomsModel";
|
||||||
|
import { addItem, getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
export const getPersonalRooms = async (accountId: string) => {
|
export const getPersonalRooms = async (accountId: string) => {
|
||||||
const personalRooms = await PersonalRooms.findOne({ personalRoomsOwnerId: accountId });
|
const personalRooms = await PersonalRooms.findOne({ personalRoomsOwnerId: accountId });
|
||||||
@ -8,3 +9,18 @@ export const getPersonalRooms = async (accountId: string) => {
|
|||||||
}
|
}
|
||||||
return personalRooms;
|
return personalRooms;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateShipFeature = async (accountId: string, shipFeature: string) => {
|
||||||
|
const personalRooms = await getPersonalRooms(accountId);
|
||||||
|
|
||||||
|
if (personalRooms.Ship.Features.includes(shipFeature)) {
|
||||||
|
throw new Error(`ship feature ${shipFeature} already unlocked`);
|
||||||
|
}
|
||||||
|
|
||||||
|
personalRooms.Ship.Features.push(shipFeature);
|
||||||
|
await personalRooms.save();
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
await addItem(inventory, shipFeature, -1);
|
||||||
|
await inventory.save();
|
||||||
|
};
|
||||||
|
34
src/services/questService.ts
Normal file
34
src/services/questService.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { IInventoryDatabase, IQuestKeyDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { HydratedDocument } from "mongoose";
|
||||||
|
|
||||||
|
export const updateQuestKey = (
|
||||||
|
inventory: HydratedDocument<IInventoryDatabase>,
|
||||||
|
questKeyUpdate: IUpdateQuestRequest["QuestKeys"]
|
||||||
|
): void => {
|
||||||
|
if (questKeyUpdate.length > 1) {
|
||||||
|
logger.error(`more than 1 quest key not supported`);
|
||||||
|
throw new Error("more than 1 quest key not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
const questKeyIndex = inventory.QuestKeys.findIndex(questKey => questKey.ItemType === questKeyUpdate[0].ItemType);
|
||||||
|
|
||||||
|
if (questKeyIndex === -1) {
|
||||||
|
throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory.QuestKeys[questKeyIndex] = questKeyUpdate[0];
|
||||||
|
|
||||||
|
if (questKeyUpdate[0].Completed) {
|
||||||
|
inventory.QuestKeys[questKeyIndex].CompletionDate = new Date();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IUpdateQuestRequest {
|
||||||
|
QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[];
|
||||||
|
PS: string;
|
||||||
|
questCompletion: boolean;
|
||||||
|
PlayerShipEvents: unknown[];
|
||||||
|
crossPlaySetting: string;
|
||||||
|
DoQuestReward: boolean;
|
||||||
|
}
|
@ -43,6 +43,7 @@ export interface IInventoryDatabase
|
|||||||
GuildId?: Types.ObjectId; // GuildId changed from ?IOid to ?Types.ObjectId
|
GuildId?: Types.ObjectId; // GuildId changed from ?IOid to ?Types.ObjectId
|
||||||
PendingRecipes: IPendingRecipe[];
|
PendingRecipes: IPendingRecipe[];
|
||||||
QuestKeys: IQuestKeyDatabase[];
|
QuestKeys: IQuestKeyDatabase[];
|
||||||
|
ActiveQuest: string;
|
||||||
BlessingCooldown: Date;
|
BlessingCooldown: Date;
|
||||||
Ships: Types.ObjectId[];
|
Ships: Types.ObjectId[];
|
||||||
WeaponSkins: IWeaponSkinDatabase[];
|
WeaponSkins: IWeaponSkinDatabase[];
|
||||||
@ -71,7 +72,7 @@ export interface IInventoryDatabase
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuestKeyDatabase {
|
export interface IQuestKeyDatabase {
|
||||||
Progress?: IQuestProgress[];
|
Progress?: IQuestStage[];
|
||||||
unlock?: boolean;
|
unlock?: boolean;
|
||||||
Completed?: boolean;
|
Completed?: boolean;
|
||||||
CustomData?: string; //TODO: check whether this actually exists
|
CustomData?: string; //TODO: check whether this actually exists
|
||||||
@ -205,7 +206,7 @@ export interface IInventoryClient extends IDailyAffiliations {
|
|||||||
RawUpgrades: IRawUpgrade[];
|
RawUpgrades: IRawUpgrade[];
|
||||||
ReceivedStartingGear: boolean;
|
ReceivedStartingGear: boolean;
|
||||||
Ships: IShipInventory[];
|
Ships: IShipInventory[];
|
||||||
QuestKeys: IQuestKeyResponse[];
|
QuestKeys: IQuestKeyClient[];
|
||||||
FlavourItems: IFlavourItem[];
|
FlavourItems: IFlavourItem[];
|
||||||
Scoops: IEquipmentDatabase[];
|
Scoops: IEquipmentDatabase[];
|
||||||
TrainingRetriesLeft: number;
|
TrainingRetriesLeft: number;
|
||||||
@ -889,14 +890,14 @@ export interface IPlayerSkills {
|
|||||||
LPS_DRIFT_ENDURANCE: number;
|
LPS_DRIFT_ENDURANCE: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuestKeyResponse extends Omit<IQuestKeyDatabase, "CompletionDate"> {
|
export interface IQuestKeyClient extends Omit<IQuestKeyDatabase, "CompletionDate"> {
|
||||||
CompletionDate?: IMongoDate;
|
CompletionDate?: IMongoDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuestProgress {
|
export interface IQuestStage {
|
||||||
c: number;
|
c?: number;
|
||||||
i: boolean;
|
i?: boolean;
|
||||||
m: boolean;
|
m?: boolean;
|
||||||
b?: any[];
|
b?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
export const inventoryFields = ["RawUpgrades", "MiscItems", "Consumables", "Recipes"] as const;
|
export const inventoryFields = ["RawUpgrades", "MiscItems", "Consumables", "Recipes"] as const;
|
||||||
export type IInventoryFieldType = (typeof inventoryFields)[number];
|
export type IInventoryFieldType = (typeof inventoryFields)[number];
|
||||||
|
|
||||||
export interface IMissionRewardResponse {
|
export interface IMissionReward {
|
||||||
StoreItem?: string;
|
StoreItem: string;
|
||||||
TypeName: string;
|
TypeName?: string;
|
||||||
UpgradeLevel?: number;
|
UpgradeLevel?: number;
|
||||||
ItemCount: number;
|
ItemCount: number;
|
||||||
TweetText: string;
|
TweetText?: string;
|
||||||
ProductCategory: string;
|
ProductCategory?: string;
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,15 @@ import { ArtifactPolarity, IPolarity, IEquipmentClient } from "@/src/types/inven
|
|||||||
import {
|
import {
|
||||||
IBooster,
|
IBooster,
|
||||||
IChallengeProgress,
|
IChallengeProgress,
|
||||||
IConsumable,
|
|
||||||
IEvolutionProgress,
|
IEvolutionProgress,
|
||||||
IMiscItem,
|
|
||||||
ITypeCount,
|
ITypeCount,
|
||||||
IMission,
|
IMission,
|
||||||
IRawUpgrade,
|
IRawUpgrade,
|
||||||
ISeasonChallenge,
|
ISeasonChallenge,
|
||||||
TSolarMapRegion,
|
TSolarMapRegion,
|
||||||
TEquipmentKey,
|
TEquipmentKey,
|
||||||
IFusionTreasure
|
IFusionTreasure,
|
||||||
|
IQuestKeyClient
|
||||||
} from "./inventoryTypes/inventoryTypes";
|
} from "./inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
export interface IThemeUpdateRequest {
|
export interface IThemeUpdateRequest {
|
||||||
@ -33,10 +32,13 @@ export interface IUpdateChallengeProgressRequest {
|
|||||||
SeasonChallengeCompletions: ISeasonChallenge[];
|
SeasonChallengeCompletions: ISeasonChallenge[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMissionInventoryUpdateRequest {
|
export type IMissionInventoryUpdateRequest = {
|
||||||
rewardsMultiplier?: number;
|
|
||||||
ActiveBoosters?: IBooster[];
|
|
||||||
AffiliationChanges?: IAffiliationChange[];
|
AffiliationChanges?: IAffiliationChange[];
|
||||||
|
crossPlaySetting?: string;
|
||||||
|
rewardsMultiplier?: number;
|
||||||
|
GoalTag: string;
|
||||||
|
LevelKeyName: string;
|
||||||
|
ActiveBoosters?: IBooster[];
|
||||||
Suits?: IEquipmentClient[];
|
Suits?: IEquipmentClient[];
|
||||||
LongGuns?: IEquipmentClient[];
|
LongGuns?: IEquipmentClient[];
|
||||||
Pistols?: IEquipmentClient[];
|
Pistols?: IEquipmentClient[];
|
||||||
@ -52,21 +54,40 @@ export interface IMissionInventoryUpdateRequest {
|
|||||||
MoaPets?: IEquipmentClient[];
|
MoaPets?: IEquipmentClient[];
|
||||||
FusionBundles?: ITypeCount[];
|
FusionBundles?: ITypeCount[];
|
||||||
RawUpgrades?: IRawUpgrade[];
|
RawUpgrades?: IRawUpgrade[];
|
||||||
MiscItems?: IMiscItem[];
|
MiscItems?: ITypeCount[];
|
||||||
Consumables?: IConsumable[];
|
Consumables?: ITypeCount[];
|
||||||
FusionTreasures?: IFusionTreasure[];
|
FusionTreasures?: IFusionTreasure[];
|
||||||
Recipes?: IConsumable[];
|
Recipes?: ITypeCount[];
|
||||||
|
QuestKeys?: IQuestKeyClient[];
|
||||||
RegularCredits?: number;
|
RegularCredits?: number;
|
||||||
ChallengeProgress?: IChallengeProgress[];
|
MissionFailed: boolean;
|
||||||
RewardInfo?: IMissionInventoryUpdateRequestRewardInfo;
|
MissionStatus: IMissionStatus;
|
||||||
|
AliveTime: number;
|
||||||
|
MissionTime: number;
|
||||||
Missions?: IMission;
|
Missions?: IMission;
|
||||||
EvolutionProgress?: IEvolutionProgress[];
|
|
||||||
LastRegionPlayed?: TSolarMapRegion;
|
LastRegionPlayed?: TSolarMapRegion;
|
||||||
|
GameModeId: number;
|
||||||
|
hosts: string[];
|
||||||
|
currentClients: unknown[];
|
||||||
|
ChallengeProgress: IChallengeProgress[];
|
||||||
|
PS: string;
|
||||||
|
ActiveDojoColorResearch: string;
|
||||||
|
RewardInfo?: IRewardInfo;
|
||||||
|
ReceivedCeremonyMsg: boolean;
|
||||||
|
LastCeremonyResetDate: number;
|
||||||
|
MissionPTS: number;
|
||||||
|
RepHash: string;
|
||||||
|
EndOfMatchUpload: boolean;
|
||||||
|
ObjectiveReached: boolean;
|
||||||
|
sharedSessionId: string;
|
||||||
|
FpsAvg: number;
|
||||||
|
FpsMin: number;
|
||||||
|
FpsMax: number;
|
||||||
|
FpsSamples: number;
|
||||||
|
EvolutionProgress?: IEvolutionProgress[];
|
||||||
|
};
|
||||||
|
|
||||||
FusionPoints?: number; // Not a part of the request, but we put it in this struct as an intermediate storage.
|
export interface IRewardInfo {
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMissionInventoryUpdateRequestRewardInfo {
|
|
||||||
node: string;
|
node: string;
|
||||||
VaultsCracked?: number; // for Spy missions
|
VaultsCracked?: number; // for Spy missions
|
||||||
rewardTier?: number;
|
rewardTier?: number;
|
||||||
@ -82,15 +103,15 @@ export interface IMissionInventoryUpdateRequestRewardInfo {
|
|||||||
rewardSeed?: number;
|
rewardSeed?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IMissionStatus = "GS_SUCCESS" | "GS_FAILURE" | "GS_DUMPED" | "GS_QUIT" | "GS_INTERRUPTED";
|
||||||
|
|
||||||
export interface IInventorySlotsRequest {
|
export interface IInventorySlotsRequest {
|
||||||
Bin: "PveBonusLoadoutBin";
|
Bin: "PveBonusLoadoutBin";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUpdateGlyphRequest {
|
export interface IUpdateGlyphRequest {
|
||||||
AvatarImageType: string;
|
AvatarImageType: string;
|
||||||
AvatarImage: string;
|
AvatarImage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUpgradesRequest {
|
export interface IUpgradesRequest {
|
||||||
ItemCategory: TEquipmentKey;
|
ItemCategory: TEquipmentKey;
|
||||||
ItemId: IOid;
|
ItemId: IOid;
|
||||||
@ -98,7 +119,6 @@ export interface IUpgradesRequest {
|
|||||||
UpgradeVersion: number;
|
UpgradeVersion: number;
|
||||||
Operations: IUpgradeOperation[];
|
Operations: IUpgradeOperation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUpgradeOperation {
|
export interface IUpgradeOperation {
|
||||||
OperationType: string;
|
OperationType: string;
|
||||||
UpgradeRequirement: string; // uniqueName of item being consumed
|
UpgradeRequirement: string; // uniqueName of item being consumed
|
||||||
@ -106,3 +126,8 @@ export interface IUpgradeOperation {
|
|||||||
PolarizeValue: ArtifactPolarity;
|
PolarizeValue: ArtifactPolarity;
|
||||||
PolarityRemap: IPolarity[];
|
PolarityRemap: IPolarity[];
|
||||||
}
|
}
|
||||||
|
export interface IUnlockShipFeatureRequest {
|
||||||
|
Feature: string;
|
||||||
|
KeyChain: string;
|
||||||
|
ChainStage: number;
|
||||||
|
}
|
||||||
|
13
static/fixed_responses/questCompletionRewards.json
Normal file
13
static/fixed_responses/questCompletionRewards.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain": [
|
||||||
|
{
|
||||||
|
"ItemType": "/Lotus/Types/Keys/DuviriQuest/DuviriQuestKeyChain",
|
||||||
|
"ItemCount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ItemType": "/Lotus/Types/NeutralCreatures/ErsatzHorse/ErsatzHorsePowerSuit",
|
||||||
|
"ItemCount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain": [{ "ItemType": "/Lotus/Types/Recipes/WarframeRecipes/BrokenFrameBlueprint", "ItemCount": 1 }]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user