From e3103d4ab62bf2311c2e1abae1b451c4cc37c6cd Mon Sep 17 00:00:00 2001 From: Sainan Date: Sat, 15 Jun 2024 15:09:38 +0200 Subject: [PATCH 1/7] fix(webui): webui config change not committing change to memory (#298) --- src/services/configService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/configService.ts b/src/services/configService.ts index 8290047b..283b31bf 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -46,5 +46,6 @@ interface ILoggerConfig { export const updateConfig = async (data: string) => { amnesia = true; - return await fsPromises.writeFile(configPath, data); + await fsPromises.writeFile(configPath, data); + Object.assign(config, JSON.parse(data)); }; From f216d5222fb44f46af09a9aa12c53f7a9fcecb31 Mon Sep 17 00:00:00 2001 From: Sainan Date: Sat, 15 Jun 2024 15:12:08 +0200 Subject: [PATCH 2/7] feat: purchase shop decorations (#299) --- src/services/inventoryService.ts | 78 ++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index f21ea25a..e5f82b97 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -116,6 +116,23 @@ export const addItem = async ( FlavourItems: [await addCustomization(typeName, accountId)] } }; + case "Objects": { + // /Lotus/Objects/Tenno/Props/TnoLisetTextProjector (Note Beacon) + const inventory = await getInventory(accountId); + const changes = [ + { + ItemType: typeName, + ItemCount: quantity + } satisfies IMiscItem + ]; + addShipDecorations(inventory, changes); + await inventory.save(); + return { + InventoryChanges: { + ShipDecorations: changes + } + }; + } case "Types": switch (typeName.substr(1).split("/")[2]) { case "AvatarImages": @@ -136,20 +153,40 @@ export const addItem = async ( } }; case "Items": { - const inventory = await getInventory(accountId); - const miscItemChanges = [ - { - ItemType: typeName, - ItemCount: quantity - } satisfies IMiscItem - ]; - addMiscItems(inventory, miscItemChanges); - await inventory.save(); - return { - InventoryChanges: { - MiscItems: miscItemChanges + switch (typeName.substr(1).split("/")[3]) { + case "ShipDecos": { + const inventory = await getInventory(accountId); + const changes = [ + { + ItemType: typeName, + ItemCount: quantity + } satisfies IMiscItem + ]; + addShipDecorations(inventory, changes); + await inventory.save(); + return { + InventoryChanges: { + ShipDecorations: changes + } + }; } - }; + default: { + const inventory = await getInventory(accountId); + const miscItemChanges = [ + { + ItemType: typeName, + ItemCount: quantity + } satisfies IMiscItem + ]; + addMiscItems(inventory, miscItemChanges); + await inventory.save(); + return { + InventoryChanges: { + MiscItems: miscItemChanges + } + }; + } + } } case "Recipes": case "Consumables": { @@ -427,6 +464,21 @@ export const addMiscItems = (inventory: IInventoryDatabaseDocument, itemsArray: }); }; +export const addShipDecorations = (inventory: IInventoryDatabaseDocument, itemsArray: IConsumable[] | undefined) => { + const { ShipDecorations } = inventory; + + itemsArray?.forEach(({ ItemCount, ItemType }) => { + const itemIndex = ShipDecorations.findIndex(miscItem => miscItem.ItemType === ItemType); + + if (itemIndex !== -1) { + ShipDecorations[itemIndex].ItemCount += ItemCount; + inventory.markModified(`ShipDecorations.${itemIndex}.ItemCount`); + } else { + ShipDecorations.push({ ItemCount, ItemType }); + } + }); +}; + export const addConsumables = (inventory: IInventoryDatabaseDocument, itemsArray: IConsumable[] | undefined) => { const { Consumables } = inventory; From 21db3b5c5d5dffd60c8f57c1818f6bd48ddbe073 Mon Sep 17 00:00:00 2001 From: OrdisPrime <134585663+OrdisPrime@users.noreply.github.com> Date: Sat, 15 Jun 2024 17:39:13 +0200 Subject: [PATCH 3/7] fix: move ship decorations (#300) --- src/models/personalRoomsModel.ts | 3 +- src/services/shipCustomizationsService.ts | 71 ++++++++++++++++++++++- src/types/shipTypes.ts | 12 +++- 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/models/personalRoomsModel.ts b/src/models/personalRoomsModel.ts index 8f4abede..8a7caaf0 100644 --- a/src/models/personalRoomsModel.ts +++ b/src/models/personalRoomsModel.ts @@ -7,7 +7,8 @@ const placedDecosSchema = new Schema( { Type: String, Pos: [Number], - Rot: [Number] + Rot: [Number], + Scale: Number }, { id: false } ); diff --git a/src/services/shipCustomizationsService.ts b/src/services/shipCustomizationsService.ts index 98125e50..d21510e7 100644 --- a/src/services/shipCustomizationsService.ts +++ b/src/services/shipCustomizationsService.ts @@ -6,6 +6,7 @@ import { IShipDecorationsRequest, IShipDecorationsResponse } from "@/src/types/shipTypes"; +import { logger } from "@/src/utils/logger"; import { Types } from "mongoose"; export const setShipCustomizations = async (shipCustomization: ISetShipCustomizationsRequest) => { @@ -37,12 +38,74 @@ export const handleSetShipDecorations = async ( const rooms = placedDecoration.IsApartment ? personalRooms.Apartment.Rooms : personalRooms.Ship.Rooms; - const room = rooms.find(room => room.Name === placedDecoration.Room); + const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room); + + if (!roomToPlaceIn) { + logger.error("room not found"); + throw new Error("room not found"); + } + + if (placedDecoration.MoveId) { + //moved within the same room + if (placedDecoration.OldRoom === placedDecoration.Room) { + const existingDecorationIndex = roomToPlaceIn?.PlacedDecos?.findIndex( + deco => deco._id.toString() === placedDecoration.MoveId + ); + + if (existingDecorationIndex === -1) { + logger.error("decoration to be moved not found"); + throw new Error("decoration to be moved not found"); + } + + roomToPlaceIn.PlacedDecos[existingDecorationIndex].Pos = placedDecoration.Pos; + roomToPlaceIn.PlacedDecos[existingDecorationIndex].Rot = placedDecoration.Rot; + + if (placedDecoration.Scale) { + roomToPlaceIn.PlacedDecos[existingDecorationIndex].Scale = placedDecoration.Scale; + } + + await personalRooms.save(); + return { + OldRoom: placedDecoration.OldRoom, + NewRoom: placedDecoration.Room, + IsApartment: placedDecoration.IsApartment, + MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal + }; + } + + //moved to a different room + const oldRoom = rooms.find(room => room.Name === placedDecoration.OldRoom); + + if (!oldRoom) { + logger.error("old room not found"); + throw new Error("old room not found"); + } + + oldRoom.PlacedDecos.pull({ _id: placedDecoration.MoveId }); + + const newDecoration = { + Type: placedDecoration.Type, + Pos: placedDecoration.Pos, + Rot: placedDecoration.Rot, + Scale: placedDecoration.Scale || 1, + _id: placedDecoration.MoveId + }; + + //the new room is still roomToPlaceIn + roomToPlaceIn.PlacedDecos.push(newDecoration); + await personalRooms.save(); + return { + OldRoom: placedDecoration.OldRoom, + NewRoom: placedDecoration.Room, + IsApartment: placedDecoration.IsApartment, + MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal + }; + } //TODO: check whether to remove from shipitems if (placedDecoration.RemoveId) { - room?.PlacedDecos?.pull({ _id: placedDecoration.RemoveId }); + roomToPlaceIn.PlacedDecos.pull({ _id: placedDecoration.RemoveId }); await personalRooms.save(); return { DecoId: placedDecoration.RemoveId, @@ -54,11 +117,13 @@ export const handleSetShipDecorations = async ( // TODO: handle capacity + //place decoration const decoId = new Types.ObjectId(); - room?.PlacedDecos?.push({ + roomToPlaceIn.PlacedDecos?.push({ Type: placedDecoration.Type, Pos: placedDecoration.Pos, Rot: placedDecoration.Rot, + Scale: placedDecoration.Scale || 1, _id: decoId }); diff --git a/src/types/shipTypes.ts b/src/types/shipTypes.ts index 53cf1f87..6b0e019f 100644 --- a/src/types/shipTypes.ts +++ b/src/types/shipTypes.ts @@ -69,6 +69,7 @@ export interface IPlacedDecosDatabase { Type: string; Pos: [number, number, number]; Rot: [number, number, number]; + Scale: number; _id: Types.ObjectId; } @@ -100,12 +101,17 @@ export interface IShipDecorationsRequest { Rot: [number, number, number]; Room: string; IsApartment: boolean; - RemoveId: string; + RemoveId?: string; + MoveId?: string; + OldRoom?: string; + Scale?: number; } export interface IShipDecorationsResponse { - DecoId: string; - Room: string; + DecoId?: string; + Room?: string; IsApartment: boolean; MaxCapacityIncrease?: number; + OldRoom?: string; + NewRoom?: string; } From 5036d6dbae41dd4e86204c710d912bff3e3c7770 Mon Sep 17 00:00:00 2001 From: Sainan Date: Sat, 15 Jun 2024 22:12:57 +0200 Subject: [PATCH 4/7] feat: purchasing of bundles (#301) --- package-lock.json | 8 ++-- package.json | 2 +- src/services/purchaseService.ts | 66 +++++++++++++++++++++++---------- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 14580188..113407a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "express": "^5.0.0-beta.3", "mongoose": "^8.1.1", "warframe-items": "^1.1262.74", - "warframe-public-export-plus": "^0.2.2", + "warframe-public-export-plus": "^0.2.3", "warframe-riven-info": "^0.1.0", "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1" @@ -3909,9 +3909,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.2.2.tgz", - "integrity": "sha512-PAsiyiRDqXcsUwZTweihwrSksd+GT3USrbHwS/TrJUC3TqLS0Ng24OfefFKPWOmPfMxDbdkg2zV39uq72iZ/Yg==" + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.2.3.tgz", + "integrity": "sha512-Bl4gb3f1LIdGXLEOJg2XTIFYqrialdTIvVhDqDzVJIRfii0PKsy9jsr9vqM14tWz7oVpQMeCUyvisDkkXijTSg==" }, "node_modules/warframe-riven-info": { "version": "0.1.0", diff --git a/package.json b/package.json index 88a71412..9da3bcca 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "express": "^5.0.0-beta.3", "mongoose": "^8.1.1", "warframe-items": "^1.1262.74", - "warframe-public-export-plus": "^0.2.2", + "warframe-public-export-plus": "^0.2.3", "warframe-riven-info": "^0.1.0", "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1" diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index ba13666a..b8a1c1ea 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -3,6 +3,7 @@ import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers"; import { addItem, addBooster, updateCurrency, updateSlots } from "@/src/services/inventoryService"; import { IPurchaseRequest, SlotPurchase } from "@/src/types/purchaseTypes"; import { logger } from "@/src/utils/logger"; +import { ExportBundles } from "warframe-public-export-plus"; export const getStoreItemCategory = (storeItem: string) => { const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/"); @@ -21,26 +22,12 @@ export const getStoreItemTypesCategory = (typesItem: string) => { export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountId: string) => { logger.debug("purchase request", purchaseRequest); - const storeCategory = getStoreItemCategory(purchaseRequest.PurchaseParams.StoreItem); - const internalName = purchaseRequest.PurchaseParams.StoreItem.replace("/StoreItems", ""); - logger.debug(`store category ${storeCategory}`); - let purchaseResponse; - switch (storeCategory) { - default: - purchaseResponse = await addItem(accountId, internalName); - break; - case "Types": - purchaseResponse = await handleTypesPurchase( - internalName, - accountId, - purchaseRequest.PurchaseParams.Quantity - ); - break; - case "Boosters": - purchaseResponse = await handleBoostersPurchase(internalName, accountId); - break; - } + const purchaseResponse = await handleStoreItemAcquisition( + purchaseRequest.PurchaseParams.StoreItem, + accountId, + purchaseRequest.PurchaseParams.Quantity + ); if (!purchaseResponse) throw new Error("purchase response was undefined"); @@ -58,6 +45,43 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI return purchaseResponse; }; +const handleStoreItemAcquisition = async ( + storeItemName: string, + accountId: string, + quantity: number +): Promise<{ InventoryChanges: object }> => { + let purchaseResponse = { + InventoryChanges: {} + }; + logger.debug(`handling acquision of ${storeItemName}`); + if (storeItemName in ExportBundles) { + const bundle = ExportBundles[storeItemName]; + logger.debug("acquiring bundle", bundle); + for (const component of bundle.components) { + purchaseResponse = { + ...purchaseResponse, + ...(await handleStoreItemAcquisition(component.typeName, accountId, component.purchaseQuantity)) + }; + } + } else { + const storeCategory = getStoreItemCategory(storeItemName); + const internalName = storeItemName.replace("/StoreItems", ""); + logger.debug(`store category ${storeCategory}`); + switch (storeCategory) { + default: + purchaseResponse = await addItem(accountId, internalName); + break; + case "Types": + purchaseResponse = await handleTypesPurchase(internalName, accountId, quantity); + break; + case "Boosters": + purchaseResponse = await handleBoostersPurchase(internalName, accountId); + break; + } + } + return purchaseResponse; +}; + export const slotPurchaseNameToSlotName: SlotPurchase = { SuitSlotItem: { name: "SuitBin", slotsPerPurchase: 1 }, TwoSentinelSlotItem: { name: "SentinelBin", slotsPerPurchase: 2 }, @@ -122,7 +146,9 @@ const boosterCollection = [ const handleBoostersPurchase = async (boosterStoreName: string, accountId: string) => { const match = boosterStoreName.match(/(\d+)Day/); - if (!match) return; + if (!match) { + return { InventoryChanges: {} }; + } const extractedDigit = Number(match[1]); const ItemType = boosterCollection.find(i => From 31da32889a9adef5f3ee93846ac48bdcfadcb2ad Mon Sep 17 00:00:00 2001 From: Mebius <160588255+Mebiius@users.noreply.github.com> Date: Sun, 16 Jun 2024 03:23:33 -0700 Subject: [PATCH 5/7] improve(webui): Zaw display label (#304) Co-authored-by: Belenus <160588255+Be1enus@users.noreply.github.com> --- static/webui/script.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/webui/script.js b/static/webui/script.js index 37fa5177..288f70c7 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -95,6 +95,8 @@ window.itemListPromise = new Promise(resolve => { "/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: "Pistol" }, "/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: "Rifle" }, "/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: "Shotgun" }, + // Modular weapons + "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": {name: "Zaw"}, // Missing in data sources "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: "Traumatic Peculiar" } }; From 8f8d58ade6cf59773d3e9f82bb53196824e20e66 Mon Sep 17 00:00:00 2001 From: OrdisPrime Date: Sun, 16 Jun 2024 10:23:52 +0000 Subject: [PATCH 6/7] Apply prettier changes --- static/webui/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/webui/script.js b/static/webui/script.js index 288f70c7..15db05d7 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -96,7 +96,7 @@ window.itemListPromise = new Promise(resolve => { "/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: "Rifle" }, "/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: "Shotgun" }, // Modular weapons - "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": {name: "Zaw"}, + "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": { name: "Zaw" }, // Missing in data sources "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: "Traumatic Peculiar" } }; From c9b570059ea3a42ddb6be70a9fc36d900eb003f9 Mon Sep 17 00:00:00 2001 From: Sainan Date: Sun, 16 Jun 2024 12:24:15 +0200 Subject: [PATCH 7/7] improve: don't fake XPInfo if we don't have to (#303) --- src/controllers/api/inventoryController.ts | 23 +++++++++++----------- static/webui/script.js | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index e78bf3d8..4aa44387 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -78,19 +78,18 @@ const inventoryController: RequestHandler = async (request: Request, response: R } } - if ( - typeof config.spoofMasteryRank === "number" && - config.spoofMasteryRank >= 0 && - config.spoofMasteryRank <= 5030 - ) { + if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) { inventoryResponse.PlayerLevel = config.spoofMasteryRank; - inventoryResponse.XPInfo = []; - let numFrames = getExpRequiredForMr(config.spoofMasteryRank) / 6000; - while (numFrames-- > 0) { - inventoryResponse.XPInfo.push({ - ItemType: "/Lotus/Powersuits/Mag/Mag", - XP: 1_600_000 - }); + if (!("xpBasedLevelCapDisabled" in request.query)) { + // This client has not been patched to accept any mastery rank, need to fake the XP. + inventoryResponse.XPInfo = []; + let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000; + while (numFrames-- > 0) { + inventoryResponse.XPInfo.push({ + ItemType: "/Lotus/Powersuits/Mag/Mag", + XP: 1_600_000 + }); + } } } diff --git a/static/webui/script.js b/static/webui/script.js index 15db05d7..0475a730 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -123,7 +123,7 @@ window.itemListPromise = new Promise(resolve => { }); function updateInventory() { - const req = $.get("/api/inventory.php?" + window.authz); + const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); req.done(data => { window.itemListPromise.then(itemMap => { document.getElementById("warframe-list").innerHTML = ""; @@ -539,7 +539,7 @@ function doAcquireRiven() { }) }).done(function () { // Get riven's assigned id - $.get("/api/inventory.php?" + window.authz).done(data => { + $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(data => { for (const rawUpgrade of data.RawUpgrades) { if (rawUpgrade.ItemType === uniqueName) { // Add fingerprint to riven