Mission rewards save #46

Merged
holmityd merged 15 commits from main into main 2023-08-31 03:29:10 -07:00
11 changed files with 292 additions and 15 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
/.env /.env
/static/data/*.bin /static/data/*.bin
yarn.lock yarn.lock
/tmp

View File

@ -1,6 +1,61 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { missionInventoryUpdate } from "@/src/services/inventoryService";
import { MissionInventoryUpdate } from "@/src/types/missionInventoryUpdateType";
/*
- [ ] crossPlaySetting
- [ ] rewardsMultiplier
- [ ] ActiveBoosters
- [x] LongGuns
- [x] Pistols
- [x] Suits
- [x] Melee
- [x] RawUpgrades
- [x] MiscItems
- [x] RegularCredits
- [ ] RandomUpgradesIdentified
- [ ] MissionFailed
- [ ] MissionStatus
- [ ] CurrentLoadOutIds
- [ ] AliveTime
- [ ] MissionTime
- [ ] Missions
- [ ] CompletedAlerts
- [ ] LastRegionPlayed
- [ ] GameModeId
- [ ] hosts
- [x] ChallengeProgress
- [ ] SeasonChallengeHistory
- [ ] PS
- [ ] ActiveDojoColorResearch
- [ ] RewardInfo
- [ ] ReceivedCeremonyMsg
- [ ] LastCeremonyResetDate
- [ ] MissionPTS
- [ ] RepHash
- [ ] EndOfMatchUpload
- [ ] ObjectiveReached
- [ ] FpsAvg
- [ ] FpsMin
- [ ] FpsMax
- [ ] FpsSamples
*/
const missionInventoryUpdateController: RequestHandler = (_req, res) => { // eslint-disable-next-line @typescript-eslint/no-misused-promises
const missionInventoryUpdateController: RequestHandler = async (req, res) => {
const [data] = String(req.body).split("\n");
const id = req.query.accountId as string;
// TODO - salt check
try {
const parsedData = JSON.parse(data) as MissionInventoryUpdate;
if (typeof parsedData !== "object" || parsedData === null) throw new Error("Invalid data format");
await missionInventoryUpdate(parsedData, id);
} catch (err) {
console.error("Error parsing JSON data:", err);
}
// TODO - get original response
res.json({}); res.json({});
}; };

View File

@ -0,0 +1,7 @@
import { RequestHandler } from "express";
const viewController: RequestHandler = (_req, res) => {
res.json({});
};
export { viewController };

View File

@ -1,5 +1,5 @@
import { Model, Schema, Types, model } from "mongoose"; import { Model, Schema, Types, model } from "mongoose";
import { FlavourItem, IInventoryDatabase } from "../types/inventoryTypes/inventoryTypes"; import { FlavourItem, RawUpgrade, MiscItem, IInventoryDatabase, Booster } from "../types/inventoryTypes/inventoryTypes";
import { Oid } from "../types/commonTypes"; import { Oid } from "../types/commonTypes";
import { ISuitDatabase, ISuitDocument } from "@/src/types/inventoryTypes/SuitTypes"; import { ISuitDatabase, ISuitDocument } from "@/src/types/inventoryTypes/SuitTypes";
import { IWeaponDatabase } from "@/src/types/inventoryTypes/weaponTypes"; import { IWeaponDatabase } from "@/src/types/inventoryTypes/weaponTypes";
@ -66,6 +66,11 @@ const WeaponSchema = new Schema({
UnlockLevel: Number UnlockLevel: Number
}); });
const BoosterSchema = new Schema({
ExpiryDate: Number,
ItemType: String
});
WeaponSchema.set("toJSON", { WeaponSchema.set("toJSON", {
transform(_document, returnedObject) { transform(_document, returnedObject) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
@ -213,7 +218,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
LoreFragmentScans: [Schema.Types.Mixed], LoreFragmentScans: [Schema.Types.Mixed],
EquippedEmotes: [String], EquippedEmotes: [String],
PendingTrades: [Schema.Types.Mixed], PendingTrades: [Schema.Types.Mixed],
Boosters: [Schema.Types.Mixed], Boosters: [BoosterSchema],
ActiveDojoColorResearch: String, ActiveDojoColorResearch: String,
SentientSpawnChanceBoosters: Schema.Types.Mixed, SentientSpawnChanceBoosters: Schema.Types.Mixed,
Affiliations: [Schema.Types.Mixed], Affiliations: [Schema.Types.Mixed],
@ -334,6 +339,9 @@ type InventoryDocumentProps = {
Pistols: Types.DocumentArray<IWeaponDatabase>; Pistols: Types.DocumentArray<IWeaponDatabase>;
Melee: Types.DocumentArray<IWeaponDatabase>; Melee: Types.DocumentArray<IWeaponDatabase>;
FlavourItems: Types.DocumentArray<FlavourItem>; FlavourItems: Types.DocumentArray<FlavourItem>;
RawUpgrades: Types.DocumentArray<RawUpgrade>;
MiscItems: Types.DocumentArray<MiscItem>;
Boosters: Types.DocumentArray<Booster>;
}; };
type InventoryModelType = Model<IInventoryDatabase, {}, InventoryDocumentProps>; type InventoryModelType = Model<IInventoryDatabase, {}, InventoryDocumentProps>;

View File

@ -1,7 +1,11 @@
AngeloTadeucci commented 2023-08-30 12:26:59 -07:00 (Migrated from github.com)
Review

routes are self-explanatory

statsRouter.get("/view.php", viewController);
routes are self-explanatory ```suggestion statsRouter.get("/view.php", viewController); ```
AngeloTadeucci commented 2023-08-30 12:26:59 -07:00 (Migrated from github.com)
Review

routes are self-explanatory

statsRouter.get("/view.php", viewController);
routes are self-explanatory ```suggestion statsRouter.get("/view.php", viewController); ```
import { viewController } from "../controllers/api/viewController";
AngeloTadeucci commented 2023-08-30 12:26:59 -07:00 (Migrated from github.com)
Review

routes are self-explanatory

statsRouter.get("/view.php", viewController);
routes are self-explanatory ```suggestion statsRouter.get("/view.php", viewController); ```
import { uploadController } from "@/src/controllers/stats/uploadController"; import { uploadController } from "@/src/controllers/stats/uploadController";
AngeloTadeucci commented 2023-08-30 12:26:59 -07:00 (Migrated from github.com)
Review

routes are self-explanatory

statsRouter.get("/view.php", viewController);
routes are self-explanatory ```suggestion statsRouter.get("/view.php", viewController); ```
import express from "express"; import express from "express";
const statsRouter = express.Router(); const statsRouter = express.Router();
statsRouter.get("/view.php", viewController);
AngeloTadeucci commented 2023-08-30 12:26:59 -07:00 (Migrated from github.com)
Review

routes are self-explanatory

statsRouter.get("/view.php", viewController);
routes are self-explanatory ```suggestion statsRouter.get("/view.php", viewController); ```
statsRouter.post("/upload.php", uploadController); statsRouter.post("/upload.php", uploadController);
AngeloTadeucci commented 2023-08-30 12:26:59 -07:00 (Migrated from github.com)
Review

routes are self-explanatory

statsRouter.get("/view.php", viewController);
routes are self-explanatory ```suggestion statsRouter.get("/view.php", viewController); ```
export { statsRouter }; export { statsRouter };

AngeloTadeucci commented 2023-08-30 12:26:59 -07:00 (Migrated from github.com)
Review

routes are self-explanatory

statsRouter.get("/view.php", viewController);
routes are self-explanatory ```suggestion statsRouter.get("/view.php", viewController); ```
AngeloTadeucci commented 2023-08-30 12:26:59 -07:00 (Migrated from github.com)
Review

routes are self-explanatory

statsRouter.get("/view.php", viewController);
routes are self-explanatory ```suggestion statsRouter.get("/view.php", viewController); ```

View File

@ -5,7 +5,13 @@ import { Types } from "mongoose";
import { ISuitResponse } from "@/src/types/inventoryTypes/SuitTypes"; import { ISuitResponse } from "@/src/types/inventoryTypes/SuitTypes";
import { SlotType } from "@/src/types/purchaseTypes"; import { SlotType } from "@/src/types/purchaseTypes";
import { IWeaponResponse } from "@/src/types/inventoryTypes/weaponTypes"; import { IWeaponResponse } from "@/src/types/inventoryTypes/weaponTypes";
import { FlavourItem } from "@/src/types/inventoryTypes/inventoryTypes"; import { ChallengeProgress, FlavourItem, IInventoryDatabaseDocument } from "@/src/types/inventoryTypes/inventoryTypes";
import {
MissionInventoryUpdate,
MissionInventoryUpdateCard,
MissionInventoryUpdateGear,
MissionInventoryUpdateItem
} from "../types/missionInventoryUpdateType";
const createInventory = async (accountOwnerId: Types.ObjectId) => { const createInventory = async (accountOwnerId: Types.ObjectId) => {
try { try {
@ -106,4 +112,97 @@ export const addCustomization = async (customizatonName: string, accountId: stri
return changedInventory.FlavourItems[flavourItemIndex].toJSON(); //mongoose bug forces as FlavourItem return changedInventory.FlavourItems[flavourItemIndex].toJSON(); //mongoose bug forces as FlavourItem
}; };
const addGearExpByCategory = (
inventory: IInventoryDatabaseDocument,
gearArray: MissionInventoryUpdateGear[] | undefined,
categoryName: "Pistols" | "LongGuns" | "Melee" | "Suits"
) => {
const category = inventory[categoryName];
gearArray?.forEach(({ ItemId, XP }) => {
const itemIndex = category.findIndex(i => i._id?.equals(ItemId.$oid));
const item = category[itemIndex];
if (itemIndex !== -1 && item.XP != undefined) {
item.XP += XP;
inventory.markModified(`${categoryName}.${itemIndex}.XP`);
}
});
};
const addItemsByCategory = (
inventory: IInventoryDatabaseDocument,
itemsArray: (MissionInventoryUpdateItem | MissionInventoryUpdateCard)[] | undefined,
categoryName: "RawUpgrades" | "MiscItems"
) => {
const category = inventory[categoryName];
itemsArray?.forEach(({ ItemCount, ItemType }) => {
const itemIndex = category.findIndex(i => i.ItemType === ItemType);
if (itemIndex !== -1) {
category[itemIndex].ItemCount += ItemCount;
inventory.markModified(`${categoryName}.${itemIndex}.ItemCount`);
} else {
category.push({ ItemCount, ItemType });
}
});
};
const addChallenges = (inventory: IInventoryDatabaseDocument, itemsArray: ChallengeProgress[] | undefined) => {
const category = inventory.ChallengeProgress;
itemsArray?.forEach(({ Name, Progress }) => {
const itemIndex = category.findIndex(i => i.Name === Name);
if (itemIndex !== -1) {
category[itemIndex].Progress += Progress;
inventory.markModified(`ChallengeProgress.${itemIndex}.ItemCount`);
} else {
category.push({ Name, Progress });
}
});
};
const gearKeys = ["Suits", "Pistols", "LongGuns", "Melee"] as const;
type GearKeysType = (typeof gearKeys)[number];
export const missionInventoryUpdate = async (data: MissionInventoryUpdate, accountId: string): Promise<void> => {
const { RawUpgrades, MiscItems, RegularCredits, ChallengeProgress } = data;
const inventory = await getInventory(accountId);
// TODO - multipliers logic
// credits
inventory.RegularCredits += RegularCredits || 0;
// gear exp
gearKeys.forEach((key: GearKeysType) => addGearExpByCategory(inventory, data[key], key));
// other
addItemsByCategory(inventory, RawUpgrades, "RawUpgrades"); // TODO - check mods fusion level
addItemsByCategory(inventory, MiscItems, "MiscItems");
addChallenges(inventory, ChallengeProgress);
await inventory.save();
};
export const addBooster = async (ItemType: string, time: number, accountId: string): Promise<void> => {
const currentTime = Math.floor(Date.now() / 1000) - 129600; // booster time getting more without 129600, probably defence logic, idk
const inventory = await getInventory(accountId);
const { Boosters } = inventory;
const itemIndex = Boosters.findIndex(i => i.ItemType === ItemType);
if (itemIndex !== -1) {
const existingBooster = Boosters[itemIndex];
existingBooster.ExpiryDate = Math.max(existingBooster.ExpiryDate, currentTime) + time;
inventory.markModified(`Boosters.${itemIndex}.ExpiryDate`);
} else {
Boosters.push({ ItemType, ExpiryDate: currentTime + time }) - 1;
}
await inventory.save();
};
export { createInventory, addPowerSuit }; export { createInventory, addPowerSuit };

View File

@ -1,13 +1,7 @@
import { getWeaponType } from "@/src/helpers/purchaseHelpers"; import { getWeaponType } from "@/src/helpers/purchaseHelpers";
import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers"; import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers";
import { import { addBooster, addCustomization, addPowerSuit, addWeapon, updateSlots } from "@/src/services/inventoryService";
addCustomization, import { IPurchaseRequest, SlotType } from "@/src/types/purchaseTypes";
addPowerSuit,
addWeapon,
updateCurrency,
updateSlots
} from "@/src/services/inventoryService";
import { IPurchaseRequest, IPurchaseResponse, SlotType } from "@/src/types/purchaseTypes";
export const getStoreItemCategory = (storeItem: string) => { export const getStoreItemCategory = (storeItem: string) => {
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/"); const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
@ -41,6 +35,9 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI
case "Types": case "Types":
purchaseResponse = await handleTypesPurchase(internalName, accountId); purchaseResponse = await handleTypesPurchase(internalName, accountId);
break; break;
case "Boosters":
purchaseResponse = await handleBoostersPurchase(internalName, accountId);
break;
default: default:
throw new Error(`unknown store category: ${storeCategory} not implemented or new`); throw new Error(`unknown store category: ${storeCategory} not implemented or new`);
@ -114,3 +111,29 @@ const handleSuitCustomizationsPurchase = async (customizationName: string, accou
} }
}; };
}; };
const boosterCollection = [
"/Lotus/Types/Boosters/ResourceAmountBooster",
"/Lotus/Types/Boosters/AffinityBooster",
"/Lotus/Types/Boosters/ResourceDropChanceBooster",
"/Lotus/Types/Boosters/CreditBooster"
];
const handleBoostersPurchase = async (boosterStoreName: string, accountId: string) => {
const match = boosterStoreName.match(/(\d+)Day/);
if (!match) return;
const extractedDigit = Number(match[1]);
const ItemType = boosterCollection.find(i =>
boosterStoreName.includes(i.split("/").pop()!.replace("Booster", ""))
)!;
const ExpiryDate = extractedDigit * 86400;
await addBooster(ItemType, ExpiryDate, accountId);
return {
InventoryChanges: {
Boosters: [{ ItemType, ExpiryDate }]
}
};
};

View File

@ -1,8 +1,11 @@
import { Oid } from "@/src/types/commonTypes"; import { Oid } from "@/src/types/commonTypes";
import { AbilityOverride, Color, Polarity } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { AbilityOverride, Color, Polarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { Document } from "mongoose"; import { Document, Types } from "mongoose";
export interface ISuitDocument extends ISuitResponse, Document {} // export interface ISuitDocument extends ISuitResponse, Document {}
export interface ISuitDocument extends Document, ISuitResponse {
_id: Types.ObjectId;
}
export interface ISuitResponse extends ISuitDatabase { export interface ISuitResponse extends ISuitDatabase {
ItemId: Oid; ItemId: Oid;
@ -20,6 +23,7 @@ export interface ISuitDatabase {
ModSlotPurchases?: number; ModSlotPurchases?: number;
FocusLens?: string; FocusLens?: string;
UnlockLevel?: number; UnlockLevel?: number;
_id: Types.ObjectId;
} }
export interface SuitConfig { export interface SuitConfig {

View File

@ -377,6 +377,16 @@ export interface FlavourItem {
ItemType: string; ItemType: string;
} }
export interface RawUpgrade {
ItemCount: number;
ItemType: string;
}
export interface MiscItem {
ItemCount: number;
ItemType: string;
}
export interface CrewshipWeapon { export interface CrewshipWeapon {
PILOT: Pilot; PILOT: Pilot;
PORT_GUNS: PortGuns; PORT_GUNS: PortGuns;
@ -919,7 +929,7 @@ export interface Progress {
export interface RawUpgrade { export interface RawUpgrade {
ItemCount: number; ItemCount: number;
LastAdded: Oid; LastAdded?: Oid;
ItemType: string; ItemType: string;
} }

View File

@ -1,5 +1,6 @@
import { Oid } from "@/src/types/commonTypes"; import { Oid } from "@/src/types/commonTypes";
import { Color, Polarity } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { Color, Polarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { Types } from "mongoose";
export interface IWeaponResponse extends IWeaponDatabase { export interface IWeaponResponse extends IWeaponDatabase {
ItemId: Oid; ItemId: Oid;
@ -20,6 +21,7 @@ export interface IWeaponDatabase {
ItemName?: string; ItemName?: string;
ModularParts?: string[]; ModularParts?: string[];
UnlockLevel?: number; UnlockLevel?: number;
_id?: Types.ObjectId;
} }
export interface WeaponConfig { export interface WeaponConfig {

View File

@ -0,0 +1,64 @@
interface MongooseId{
$oid: string;
}
interface ExpireDate{
$date: {
$numberLong: string;
}
}
export interface MissionInventoryUpdateGear{
ItemType: string;
ItemName: string;
ItemId: MongooseId;
XP: number;
UpgradeVer: number;
Features: number;
Polarized: number;
CustomizationSlotPurchases: number;
ModSlotPurchases: number;
FocusLens: string;
Expiry: ExpireDate;
Polarity: any[];
Configs: any[];
ModularParts: any[];
SkillTree: string;
UpgradeType: string;
UpgradeFingerprint: string;
OffensiveUpgrade: string;
DefensiveUpgrade: string;
UpgradesExpiry: ExpireDate;
ArchonCrystalUpgrades: any[];
}
export interface MissionInventoryUpdateItem{
ItemCount: number;
ItemType: string;
}
export interface MissionInventoryUpdateCard extends MissionInventoryUpdateItem{
ItemId: MongooseId;
UpgradeFingerprint: string;
PendingRerollFingerprint: string;
LastAdded: MongooseId;
}
interface MissionInventoryUpdateChallange{
Name: string;
Progress: number;
Completed: any[];
}
export interface MissionInventoryUpdate{
rewardsMultiplier?: number;
ActiveBoosters?: any[];
LongGuns?: MissionInventoryUpdateGear[];
Pistols?: MissionInventoryUpdateGear[];
Suits?: MissionInventoryUpdateGear[];
Melee?: MissionInventoryUpdateGear[];
RawUpgrades?: MissionInventoryUpdateCard[];
MiscItems?: MissionInventoryUpdateItem[];
RegularCredits?: number;
ChallengeProgress?: MissionInventoryUpdateChallange[];
}