feat: focus 2.0 #2898

Merged
Sainan merged 10 commits from focus-2.0 into main 2025-10-16 00:48:02 -07:00
4 changed files with 166 additions and 25 deletions

View File

@ -1,23 +1,91 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getInventory, addMiscItems, addEquipment, occupySlot } from "../../services/inventoryService.ts";
import type { IMiscItem, TFocusPolarity, TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
import { logger } from "../../utils/logger.ts";
import { ExportFocusUpgrades } from "warframe-public-export-plus";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
import { version_compare } from "../../helpers/inventoryHelpers.ts";
export const focusController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
switch (req.query.op) {
const account = await getAccountForRequest(req);
let op = req.query.op as string;
const focus2 = account.BuildLabel && version_compare(account.BuildLabel, "2022.04.29.12.53") < 0;
if (focus2) {
// Focus 2.0
switch (req.query.op) {
case Focus2Operation.InstallLens:
op = "InstallLens";
break;
case Focus2Operation.UnlockWay:
op = "UnlockWay";
break;
case Focus2Operation.UnlockUpgrade:
op = "UnlockUpgrade";
break;
case Focus2Operation.IncreasePool:
op = "IncreasePool";
break;
case Focus2Operation.LevelUpUpgrade:
op = "LevelUpUpgrade";
break;
case Focus2Operation.ActivateWay:
op = "ActivateWay";
break;
case Focus2Operation.UpdateUpgrade:
op = "UpdateUpgrade";
break;
case Focus2Operation.SentTrainingAmplifier:
op = "SentTrainingAmplifier";
break;
case Focus2Operation.UnbindUpgrade:
op = "UnbindUpgrade";
break;
case Focus2Operation.ConvertShard:
op = "ConvertShard";
break;
}
} else {
// Focus 3.0
switch (req.query.op) {
case Focus3Operation.InstallLens:
op = "InstallLens";
break;
case Focus3Operation.UnlockWay:
op = "UnlockWay";
break;
case Focus3Operation.UnlockUpgrade:
op = "UnlockUpgrade";
break;
case Focus3Operation.LevelUpUpgrade:
op = "LevelUpUpgrade";
break;
case Focus3Operation.ActivateWay:
op = "ActivateWay";
break;
case Focus3Operation.SentTrainingAmplifier:
op = "SentTrainingAmplifier";
break;
case Focus3Operation.UnbindUpgrade:
op = "UnbindUpgrade";
break;
case Focus3Operation.ConvertShard:
op = "ConvertShard";
break;
}
}
switch (op) {
default:
logger.error("Unhandled focus op type: " + String(req.query.op));
logger.debug(String(req.body));
res.end();
break;
case FocusOperation.InstallLens: {
case "InstallLens": {
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
const inventory = await getInventory(accountId);
const inventory = await getInventory(account._id.toString());
const item = inventory[request.Category].id(request.WeaponId);
if (item) {
item.FocusLens = request.LensType;
@ -35,10 +103,10 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case FocusOperation.UnlockWay: {
case "UnlockWay": {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
const focusPolarity = focusTypeToPolarity(focusType);
const inventory = await getInventory(accountId, "FocusAbility FocusUpgrades FocusXP");
const inventory = await getInventory(account._id.toString(), "FocusAbility FocusUpgrades FocusXP");
const cost = inventory.FocusAbility ? 50_000 : 0;
inventory.FocusAbility ??= focusType;
inventory.FocusUpgrades.push({ ItemType: focusType });
@ -52,12 +120,29 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case FocusOperation.ActivateWay: {
case "IncreasePool": {
const request = JSON.parse(String(req.body)) as IIncreasePoolRequest;
const focusPolarity = focusTypeToPolarity(request.FocusType);
const inventory = await getInventory(account._id.toString(), "FocusXP FocusCapacity");
let cost = 0;
for (let capacity = request.CurrentTotalCapacity; capacity != request.NewTotalCapacity; ++capacity) {
cost += increasePoolCost[capacity - 5];
}
inventory.FocusXP![focusPolarity]! -= cost;
inventory.FocusCapacity = request.NewTotalCapacity;
await inventory.save();
res.json({
TotalCapacity: request.NewTotalCapacity,
FocusPointCosts: { [focusPolarity]: cost }
});
break;
}
case "ActivateWay": {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
await Inventory.updateOne(
{
accountOwnerId: accountId
accountOwnerId: account._id.toString()
},
{
FocusAbility: focusType
@ -69,13 +154,20 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case FocusOperation.UnlockUpgrade: {
case "UnlockUpgrade": {
const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(accountId);
const inventory = await getInventory(account._id.toString());
let cost = 0;
for (const focusType of request.FocusTypes) {
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
if (focusType in ExportFocusUpgrades) {
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
} else if (focusType == "/Lotus/Upgrades/Focus/Power/Residual/ChannelEfficiencyFocusUpgrade") {
// Zenurik's Inner Might (Focus 2.0)
cost += 50_000;
} else {
logger.warn(`unknown focus upgrade ${focusType}, will unlock it for free`);
}
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
}
inventory.FocusXP![focusPolarity]! -= cost;
@ -86,15 +178,20 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case FocusOperation.LevelUpUpgrade: {
case "LevelUpUpgrade":
case "UpdateUpgrade": {
const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType);
const inventory = await getInventory(accountId);
const inventory = await getInventory(account._id.toString());
let cost = 0;
for (const focusUpgrade of request.FocusInfos) {
cost += focusUpgrade.FocusXpCost;
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
focusUpgradeDb.Level = focusUpgrade.Level;
if (op == "UpdateUpgrade") {
focusUpgradeDb.IsActive = focusUpgrade.IsActive;
} else {
focusUpgradeDb.Level = focusUpgrade.Level;
}
}
inventory.FocusXP![focusPolarity]! -= cost;
await inventory.save();
@ -104,9 +201,9 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case FocusOperation.SentTrainingAmplifier: {
case "SentTrainingAmplifier": {
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
const inventory = await getInventory(accountId);
const inventory = await getInventory(account._id.toString());
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
ModularParts: [
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
@ -119,10 +216,10 @@ export const focusController: RequestHandler = async (req, res) => {
res.json(inventoryChanges.OperatorAmps![0]);
break;
}
case FocusOperation.UnbindUpgrade: {
case "UnbindUpgrade": {
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(accountId);
const inventory = await getInventory(account._id.toString());
inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length;
addMiscItems(inventory, [
{
@ -149,7 +246,7 @@ export const focusController: RequestHandler = async (req, res) => {
});
break;
}
case FocusOperation.ConvertShard: {
case "ConvertShard": {
const request = JSON.parse(String(req.body)) as IConvertShardRequest;
// Tally XP
let xp = 0;
@ -167,7 +264,7 @@ export const focusController: RequestHandler = async (req, res) => {
for (const shard of request.Shards) {
shard.ItemCount *= -1;
}
const inventory = await getInventory(accountId);
const inventory = await getInventory(account._id.toString());
const polarity = request.Polarity;
inventory.FocusXP ??= {};
inventory.FocusXP[polarity] ??= 0;
@ -179,7 +276,8 @@ export const focusController: RequestHandler = async (req, res) => {
}
};
enum FocusOperation {
// Focus 3.0
enum Focus3Operation {
InstallLens = "1",
UnlockWay = "2",
UnlockUpgrade = "3",
@ -190,6 +288,20 @@ enum FocusOperation {
ConvertShard = "9"
}
// Focus 2.0
enum Focus2Operation {
InstallLens = "1",
UnlockWay = "2",
UnlockUpgrade = "3",
IncreasePool = "4",
LevelUpUpgrade = "5",
ActivateWay = "6",
UpdateUpgrade = "7", // used to change the IsActive state, same format as ILevelUpUpgradeRequest
SentTrainingAmplifier = "9",
UnbindUpgrade = "10",
ConvertShard = "11"
}
// For UnlockWay & ActivateWay
interface IWayRequest {
FocusType: string;
@ -199,6 +311,13 @@ interface IUnlockUpgradeRequest {
FocusTypes: string[];
}
// Focus 2.0
interface IIncreasePoolRequest {
FocusType: string;
CurrentTotalCapacity: number;
NewTotalCapacity: number;
}
interface ILevelUpUpgradeRequest {
FocusInfos: {
ItemType: string;
@ -206,6 +325,7 @@ interface ILevelUpUpgradeRequest {
IsUniversal: boolean;
Level: number;
IsActiveAbility: boolean;
IsActive?: number; // Focus 2.0
}[];
}
@ -240,3 +360,19 @@ const shardValues = {
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000,
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantTierTwoItem": 40_000
};
// Starting at a capacity of 5 (Source: https://wiki.warframe.com/w/Focus_2.0)
const increasePoolCost = [
2576, 3099, 3638, 4190, 4755, 5331, 5918, 6514, 7120, 7734, 8357, 8988, 9626, 10271, 10923, 11582, 12247, 12918,
13595, 14277, 14965, 15659, 16357, 17061, 17769, 18482, 19200, 19922, 20649, 21380, 22115, 22854, 23597, 24344,
25095, 25850, 26609, 27371, 28136, 28905, 29678, 30454, 31233, 32015, 32801, 33590, 34382, 35176, 35974, 36775,
37579, 38386, 39195, 40008, 40823, 41641, 42461, 43284, 44110, 44938, 45769, 46603, 47439, 48277, 49118, 49961,
50807, 51655, 52505, 53357, 54212, 55069, 55929, 56790, 57654, 58520, 59388, 60258, 61130, 62005, 62881, 63759,
64640, 65522, 66407, 67293, 68182, 69072, 69964, 70858, 71754, 72652, 73552, 74453, 75357, 76262, 77169, 78078,
78988, 79900, 80814, 81730, 82648, 83567, 84488, 85410, 86334, 87260, 88188, 89117, 90047, 90980, 91914, 92849,
93786, 94725, 95665, 96607, 97550, 98495, 99441, 100389, 101338, 102289, 103241, 104195, 105150, 106107, 107065,
108024, 108985, 109948, 110911, 111877, 112843, 113811, 114780, 115751, 116723, 117696, 118671, 119647, 120624,
121603, 122583, 123564, 124547, 125531, 126516, 127503, 128490, 129479, 130470, 131461, 132454, 133448, 134443,
135440, 136438, 137437, 138437, 139438, 140441, 141444, 142449, 143455, 144463, 145471, 146481, 147492, 148503,
149517
];

View File

@ -146,7 +146,8 @@ const focusUpgradeSchema = new Schema<IFocusUpgrade>(
{
ItemType: String,
Level: Number,
IsUniversal: Boolean
IsUniversal: Boolean,
IsActive: Number
},
{ _id: false }
);
@ -1542,6 +1543,8 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
FocusAbility: String,
//The treeways of the Focus school.(Active and passive Ability)
FocusUpgrades: [focusUpgradeSchema],
//Focus 2.0 Pool
FocusCapacity: Number,
//Achievement
ChallengeProgress: [challengeProgressSchema],

View File

@ -340,6 +340,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
EmailItems: ITypeCount[];
CompletedSyndicates: string[];
FocusXP?: IFocusXP;
FocusCapacity?: number;
Wishlist: string[];
Alignment?: IAlignment;
CompletedSorties: string[];
@ -638,6 +639,7 @@ export interface IFocusUpgrade {
ItemType: string;
Level?: number;
IsUniversal?: boolean;
IsActive?: number; // Focus 2.0
}
export interface IFocusXP {

View File

@ -3276,13 +3276,13 @@ function unlockFocusSchool(upgradeType) {
return new Promise(resolve => {
// Deselect current FocusAbility so we will be able to unlock the way for free
$.post({
url: "/api/focus.php?" + window.authz + "&op=5",
url: "/api/focus.php?" + window.authz + "&op=ActivateWay",
contentType: "text/plain",
data: JSON.stringify({ FocusType: null })
}).done(function () {
// Unlock the way now
$.post({
url: "/api/focus.php?" + window.authz + "&op=2",
url: "/api/focus.php?" + window.authz + "&op=UnlockWay",
contentType: "text/plain",
data: JSON.stringify({
FocusType: upgradeType