merge upstream
All checks were successful
Build / build (pull_request) Successful in 2m2s

This commit is contained in:
2025-11-09 08:25:26 -08:00
31 changed files with 519 additions and 184 deletions

View File

@@ -3,7 +3,22 @@ import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
export const addFriendImageController: RequestHandler = async (req, res) => {
export const addFriendImageGetController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
await Inventory.updateOne(
{
accountOwnerId: accountId
},
{
ActiveAvatarImageType: String(req.query.avatarImageType)
}
);
res.json({});
};
export const addFriendImagePostController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const json = getJSONfromString<IUpdateGlyphRequest>(String(req.body));

View File

@@ -1,13 +1,14 @@
import { toOid } from "../../helpers/inventoryHelpers.ts";
import { toOid2 } from "../../helpers/inventoryHelpers.ts";
import { Friendship } from "../../models/friendModel.ts";
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "../../services/friendService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import type { IFriendInfo } from "../../types/friendTypes.ts";
import type { Request, RequestHandler, Response } from "express";
// POST with {} instead of GET as of 38.5.0
export const getFriendsController: RequestHandler = async (req: Request, res: Response) => {
const accountId = await getAccountIdForRequest(req);
const account = await getAccountForRequest(req);
const accountId = account._id.toString();
const response: IGetFriendsResponse = {
Current: [],
IncomingFriendRequests: [],
@@ -20,14 +21,14 @@ export const getFriendsController: RequestHandler = async (req: Request, res: Re
for (const externalFriendship of externalFriendships) {
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
response.IncomingFriendRequests.push({
_id: toOid(externalFriendship.owner),
_id: toOid2(externalFriendship.owner, account.BuildLabel),
Note: externalFriendship.Note
});
}
}
for (const internalFriendship of internalFriendships) {
const friendInfo: IFriendInfo = {
_id: toOid(internalFriendship.friend)
_id: toOid2(internalFriendship.friend, account.BuildLabel)
};
if (externalFriendships.find(x => x.owner.equals(internalFriendship.friend))) {
response.Current.push(friendInfo);

View File

@@ -34,10 +34,9 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
const weapon = inventory[data.Category][weaponIndex];
weapon.Features ??= 0;
weapon.Features |= EquipmentFeatures.GILDED;
if (data.Recipe != "webui") {
weapon.ItemName = data.ItemName;
weapon.XP = 0;
}
weapon.ItemName = data.ItemName;
weapon.XP = 0;
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
weapon.Polarity = [
{
@@ -52,22 +51,20 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
const affiliationMods = [];
if (data.Recipe != "webui") {
const recipe = ExportRecipes[data.Recipe];
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
ItemType: ingredient.ItemType,
ItemCount: ingredient.ItemCount * -1
}));
addMiscItems(inventory, inventoryChanges.MiscItems);
const recipe = ExportRecipes[data.Recipe];
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
ItemType: ingredient.ItemType,
ItemCount: ingredient.ItemCount * -1
}));
addMiscItems(inventory, inventoryChanges.MiscItems);
if (recipe.syndicateStandingChange) {
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
affiliation.Standing += recipe.syndicateStandingChange.value;
affiliationMods.push({
Tag: recipe.syndicateStandingChange.tag,
Standing: recipe.syndicateStandingChange.value
});
}
if (recipe.syndicateStandingChange) {
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
affiliation.Standing += recipe.syndicateStandingChange.value;
affiliationMods.push({
Tag: recipe.syndicateStandingChange.tag,
Standing: recipe.syndicateStandingChange.value
});
}
await inventory.save();

View File

@@ -4,7 +4,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { TPartialStartingGear } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express";
export const giveStartingGearController: RequestHandler = async (req, res) => {
export const giveStartingGearPostController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const startingGear = getJSONfromString<TPartialStartingGear>(String(req.body));
const inventory = await getInventory(accountId);
@@ -14,3 +14,28 @@ export const giveStartingGearController: RequestHandler = async (req, res) => {
res.send(inventoryChanges);
};
export const giveStartingGearGetController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const inventoryChanges = await addStartingGear(inventory, {
Suits: [
{
ItemType: String(req.query.warframeName),
ItemId: { $oid: "0" },
Configs: []
}
],
LongGuns: [
{
ItemType: "/Lotus/Weapons/Tenno/Rifle/StartingRifle",
ItemId: { $oid: "0" },
Configs: []
}
]
});
await inventory.save();
res.send(inventoryChanges); // Not sure if this is even needed
};

View File

@@ -271,7 +271,11 @@ export const inventoryController: RequestHandler = async (request, response) =>
await inventory.save();
response.json(
await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query, account.BuildLabel)
await getInventoryResponse(
inventory,
"xpBasedLevelCapDisabled" in request.query,
"ignoreBuildLabel" in request.query ? undefined : account.BuildLabel
)
);
};
@@ -410,6 +414,7 @@ export const getInventoryResponse = async (
for (const equipment of inventoryResponse[key]) {
equipment.Features ??= 0;
equipment.Features |= EquipmentFeatures.ARCANE_SLOT;
equipment.Features |= EquipmentFeatures.SECOND_ARCANE_SLOT;
}
}
}

View File

@@ -114,12 +114,13 @@ const createLoginResponse = (
Nonce: account.Nonce,
BuildLabel: buildLabel
};
if (version_compare(buildLabel, "2014.10.24.08.24") >= 0) {
// U15 and up
if (version_compare(buildLabel, "2014.04.10.17.47") >= 0) {
// U13 and up
resp.CountryCode = account.CountryCode;
} else {
// U8
resp.NatHash = "0";
// U12 and down
resp.NatHash =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
resp.SteamId = "0";
}
if (version_compare(buildLabel, "2015.02.13.10.41") >= 0) {

View File

@@ -17,14 +17,13 @@ interface ITrainingResultsResponse {
InventoryChanges: IInventoryChanges;
}
const trainingResultController: RequestHandler = async (req, res): Promise<void> => {
const accountId = await getAccountIdForRequest(req);
const trainingResults = getJSONfromString<ITrainingResultsRequest>(String(req.body));
const handleTrainingProgress = async (
accountId: string,
numLevelsGained: number
): Promise<ITrainingResultsResponse> => {
const inventory = await getInventory(accountId, "TrainingDate PlayerLevel TradesRemaining noMasteryRankUpCooldown");
if (trainingResults.numLevelsGained == 1) {
if (numLevelsGained === 1) {
let time = Date.now();
if (!inventory.noMasteryRankUpCooldown) {
time += unixTimesInMs.hour * 23;
@@ -67,13 +66,27 @@ const trainingResultController: RequestHandler = async (req, res): Promise<void>
const changedinventory = await inventory.save();
res.json({
return {
NewTrainingDate: {
$date: { $numberLong: changedinventory.TrainingDate.getTime().toString() }
},
NewLevel: trainingResults.numLevelsGained == 1 ? changedinventory.PlayerLevel : inventory.PlayerLevel,
NewLevel: numLevelsGained == 1 ? changedinventory.PlayerLevel : inventory.PlayerLevel,
InventoryChanges: {}
} satisfies ITrainingResultsResponse);
};
};
export { trainingResultController };
export const trainingResultPostController: RequestHandler = async (req, res): Promise<void> => {
const accountId = await getAccountIdForRequest(req);
const { numLevelsGained } = getJSONfromString<ITrainingResultsRequest>(String(req.body));
const response = await handleTrainingProgress(accountId, numLevelsGained);
res.json(response satisfies ITrainingResultsResponse);
};
export const trainingResultGetController: RequestHandler = async (req, res): Promise<void> => {
const accountId = await getAccountIdForRequest(req);
const numLevelsGained = Number(req.query.numLevelsGained ?? 0);
const response = await handleTrainingProgress(accountId, numLevelsGained);
res.json(response satisfies ITrainingResultsResponse);
};

View File

@@ -2,7 +2,7 @@ import type { RequestHandler } from "express";
import { config, syncConfigWithDatabase } from "../../services/configService.ts";
import { getAccountForRequest, isAdministrator } from "../../services/loginService.ts";
import { saveConfig } from "../../services/configWriterService.ts";
import { sendWsBroadcastEx } from "../../services/wsService.ts";
import { sendWsBroadcastEx, sendWsBroadcast } from "../../services/wsService.ts";
export const getConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
@@ -21,11 +21,14 @@ export const getConfigController: RequestHandler = async (req, res) => {
export const setConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
let isWorldStateUpdate = false;
for (const [id, value] of Object.entries(req.body as Record<string, boolean | string | number>)) {
if (id.startsWith("worldState")) isWorldStateUpdate = true;
const [obj, idx] = configIdToIndexable(id);
obj[idx] = value;
}
sendWsBroadcastEx({ config_reloaded: true }, undefined, parseInt(String(req.query.wsid)));
if (isWorldStateUpdate) sendWsBroadcast({ sync_world_state: true });
syncConfigWithDatabase();
await saveConfig();
res.end();

View File

@@ -0,0 +1,34 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { EquipmentFeatures } from "../../types/equipmentTypes.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
export const equipmentFeaturesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const category = req.query.Category as TEquipmentKey;
const inventory = await getInventory(
accountId,
`${category} unlockDoubleCapacityPotatoesEverywhere unlockExilusEverywhere unlockArcanesEverywhere`
);
const bit = Number(req.query.bit) as EquipmentFeatures;
if (
(inventory.unlockDoubleCapacityPotatoesEverywhere && bit === EquipmentFeatures.DOUBLE_CAPACITY) ||
(inventory.unlockExilusEverywhere && bit === EquipmentFeatures.UTILITY_SLOT) ||
(inventory.unlockArcanesEverywhere &&
(bit === EquipmentFeatures.ARCANE_SLOT || bit === EquipmentFeatures.SECOND_ARCANE_SLOT))
) {
res.status(400).end();
}
const item = inventory[category].id(req.query.ItemId as string);
if (item) {
item.Features ??= 0;
item.Features ^= bit;
await inventory.save();
sendWsBroadcastTo(accountId, { sync_inventory: true });
res.status(200).end();
} else {
res.status(400).end();
}
};

View File

@@ -12,11 +12,16 @@ export const importController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = req.body as IImportRequest;
let anyKnownKey = false;
const inventory = await getInventory(accountId);
importInventory(inventory, request.inventory);
await inventory.save();
if (importInventory(inventory, request.inventory)) {
anyKnownKey = true;
await inventory.save();
}
if ("LoadOutPresets" in request.inventory && request.inventory.LoadOutPresets) {
anyKnownKey = true;
const loadout = await getLoadout(accountId);
importLoadOutPresets(loadout, request.inventory.LoadOutPresets);
await loadout.save();
@@ -27,12 +32,13 @@ export const importController: RequestHandler = async (req, res) => {
"Apartment" in request.inventory ||
"TailorShop" in request.inventory
) {
anyKnownKey = true;
const personalRooms = await getPersonalRooms(accountId);
importPersonalRooms(personalRooms, request.inventory);
await personalRooms.save();
}
res.end();
res.json(anyKnownKey);
broadcastInventoryUpdate(req);
};

View File

@@ -1,11 +1,10 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { getStats } from "../../services/statsService.ts";
import type { IStatsClient } from "../../types/statTypes.ts";
const viewController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const accountId = String(req.query.id ?? req.query.lookupId);
const inventory = await getInventory(accountId, "XPInfo");
const playerStats = await getStats(accountId);

View File

@@ -4,7 +4,7 @@ import { abortDojoComponentController } from "../controllers/api/abortDojoCompon
import { abortDojoComponentDestructionController } from "../controllers/api/abortDojoComponentDestructionController.ts";
import { activateRandomModController } from "../controllers/api/activateRandomModController.ts";
import { addFriendController } from "../controllers/api/addFriendController.ts";
import { addFriendImageController } from "../controllers/api/addFriendImageController.ts";
import { addFriendImageGetController, addFriendImagePostController } from "../controllers/api/addFriendImageController.ts";
import { addIgnoredUserController } from "../controllers/api/addIgnoredUserController.ts";
import { addPendingFriendController } from "../controllers/api/addPendingFriendController.ts";
import { addToAllianceController } from "../controllers/api/addToAllianceController.ts";
@@ -80,7 +80,7 @@ import { giveKeyChainTriggeredItemsController } from "../controllers/api/giveKey
import { giveKeyChainTriggeredMessageController } from "../controllers/api/giveKeyChainTriggeredMessageController.ts";
import { giveQuestKeyRewardController } from "../controllers/api/giveQuestKeyRewardController.ts";
import { giveShipDecoAndLoreFragmentController } from "../controllers/api/giveShipDecoAndLoreFragmentController.ts";
import { giveStartingGearController } from "../controllers/api/giveStartingGearController.ts";
import { giveStartingGearGetController, giveStartingGearPostController } from "../controllers/api/giveStartingGearController.ts";
import { guildTechController } from "../controllers/api/guildTechController.ts";
import { hostSessionController } from "../controllers/api/hostSessionController.ts";
import { hubBlessingController } from "../controllers/api/hubBlessingController.ts";
@@ -158,7 +158,7 @@ import { syndicateSacrificeController } from "../controllers/api/syndicateSacrif
import { syndicateStandingBonusController } from "../controllers/api/syndicateStandingBonusController.ts";
import { tauntHistoryController } from "../controllers/api/tauntHistoryController.ts";
import { tradingController } from "../controllers/api/tradingController.ts";
import { trainingResultController } from "../controllers/api/trainingResultController.ts";
import { trainingResultGetController, trainingResultPostController } from "../controllers/api/trainingResultController.ts";
import { umbraController } from "../controllers/api/umbraController.ts";
import { unlockShipFeatureController } from "../controllers/api/unlockShipFeatureController.ts";
import { updateAlignmentController } from "../controllers/api/updateAlignmentController.ts";
@@ -178,6 +178,7 @@ const apiRouter = express.Router();
// get
apiRouter.get("/abandonLibraryDailyTask.php", abandonLibraryDailyTaskController);
apiRouter.get("/abortDojoComponentDestruction.php", abortDojoComponentDestructionController);
apiRouter.get("/addFriendImage.php", addFriendImageGetController) // U17 and below
apiRouter.get("/apartment.php", apartmentController);
apiRouter.get("/cancelGuildAdvertisement.php", cancelGuildAdvertisementController);
apiRouter.get("/changeDojoRoot.php", changeDojoRootController);
@@ -208,6 +209,7 @@ apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
apiRouter.get("/getPastWeeklyChallenges.php", getPastWeeklyChallengesController)
apiRouter.get("/getShip.php", getShipController);
apiRouter.get("/getShipDecos.php", (_req, res) => { res.end(); }); // needed to log in on U22.8
apiRouter.get("/giveStartingGear.php", giveStartingGearGetController);
apiRouter.get("/getVendorInfo.php", getVendorInfoController);
apiRouter.get("/hub", hubController);
apiRouter.get("/hubInstances", hubInstancesController);
@@ -235,6 +237,7 @@ apiRouter.get("/startLibraryDailyTask.php", startLibraryDailyTaskController);
apiRouter.get("/startLibraryPersonalTarget.php", startLibraryPersonalTargetController);
apiRouter.get("/surveys.php", surveysController);
apiRouter.get("/trading.php", tradingController);
apiRouter.get("/trainingResult.php", trainingResultGetController);
apiRouter.get("/updateSession.php", updateSessionGetController);
apiRouter.get("/upgradeOperator.php", upgradeOperatorController);
apiRouter.get("/worldState.php", worldStateController); // U8
@@ -243,7 +246,7 @@ apiRouter.get("/worldState.php", worldStateController); // U8
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
apiRouter.post("/activateRandomMod.php", activateRandomModController);
apiRouter.post("/addFriend.php", addFriendController);
apiRouter.post("/addFriendImage.php", addFriendImageController);
apiRouter.post("/addFriendImage.php", addFriendImagePostController);
apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
apiRouter.post("/addPendingFriend.php", addPendingFriendController);
apiRouter.post("/addToAlliance.php", addToAllianceController);
@@ -297,7 +300,7 @@ apiRouter.post("/giveKeyChainTriggeredItems.php", giveKeyChainTriggeredItemsCont
apiRouter.post("/giveKeyChainTriggeredMessage.php", giveKeyChainTriggeredMessageController);
apiRouter.post("/giveQuestKeyReward.php", giveQuestKeyRewardController);
apiRouter.post("/giveShipDecoAndLoreFragment.php", giveShipDecoAndLoreFragmentController);
apiRouter.post("/giveStartingGear.php", giveStartingGearController);
apiRouter.post("/giveStartingGear.php", giveStartingGearPostController);
apiRouter.post("/guildTech.php", guildTechController);
apiRouter.post("/hostSession.php", hostSessionController);
apiRouter.post("/hubBlessing.php", hubBlessingController);
@@ -356,7 +359,7 @@ apiRouter.post("/stepSequencers.php", stepSequencersController);
apiRouter.post("/syndicateSacrifice.php", syndicateSacrificeController);
apiRouter.post("/syndicateStandingBonus.php", syndicateStandingBonusController);
apiRouter.post("/tauntHistory.php", tauntHistoryController);
apiRouter.post("/trainingResult.php", trainingResultController);
apiRouter.post("/trainingResult.php", trainingResultPostController);
apiRouter.post("/umbra.php", umbraController);
apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController);
apiRouter.post("/updateAlignment.php", updateAlignmentController);

View File

@@ -1,6 +1,7 @@
import express from "express";
import { tunablesController } from "../controllers/custom/tunablesController.ts";
import { equipmentFeaturesController } from "../controllers/custom/equipmentFeaturesController.ts";
import { getItemListsController } from "../controllers/custom/getItemListsController.ts";
import { pushArchonCrystalUpgradeController } from "../controllers/custom/pushArchonCrystalUpgradeController.ts";
import { popArchonCrystalUpgradeController } from "../controllers/custom/popArchonCrystalUpgradeController.ts";
@@ -53,6 +54,7 @@ import { getConfigController, setConfigController } from "../controllers/custom/
const customRouter = express.Router();
customRouter.get("/tunables.json", tunablesController);
customRouter.get("/equipmentFeatures", equipmentFeaturesController);
customRouter.get("/getItemLists", getItemListsController);
customRouter.get("/pushArchonCrystalUpgrade", pushArchonCrystalUpgradeController);
customRouter.get("/popArchonCrystalUpgrade", popArchonCrystalUpgradeController);

View File

@@ -6,6 +6,7 @@ import { leaderboardController } from "../controllers/stats/leaderboardControlle
const statsRouter = express.Router();
statsRouter.get("/view.php", viewController);
statsRouter.get("/profileStats.php", viewController);
statsRouter.post("/upload.php", uploadController);
statsRouter.post("/leaderboardWeekly.php", leaderboardController);
statsRouter.post("/leaderboardArchived.php", leaderboardController);

View File

@@ -423,3 +423,45 @@ export const getConquest = (
RandomSeed: rng.randomInt(0, 1_000_000)
};
};
export const getMissionTypeForLegacyOverride = (missionType: TMissionType, conquestType: TConquestType): string => {
if (missionType == "MT_ENDLESS_CAPTURE") {
return "EndlessCapture";
}
let str = missionType.substring(3, 4).toUpperCase() + missionType.substring(4).toLowerCase();
if (str == "Artifact") {
str = "Disruption";
}
if (str == "Defense" && conquestType == "CT_LAB") {
str = "DualDefense";
}
return str;
};
export const factionToInt = (faction: TFaction | "FC_TENNO"): number => {
switch (faction) {
case "FC_GRINEER":
return 0;
case "FC_CORPUS":
return 1;
case "FC_INFESTATION":
return 2;
case "FC_OROKIN":
return 3;
case "FC_RED_VEIL":
return 4;
case "FC_SENTIENT":
return 5;
case "FC_NARMER":
return 6;
case "FC_MITW":
return 7;
case "FC_SCALDRA":
return 8;
case "FC_TECHROT":
return 9;
case "FC_DUVIRI":
return 10;
}
throw new Error(`unexpected faction ${faction}`);
};

View File

@@ -254,17 +254,21 @@ const convertItemConfig = <T extends IItemConfig>(client: T): T => {
};
};
export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<IInventoryClient>): void => {
export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<IInventoryClient>): boolean => {
let anyKnownKey = false;
for (const key of equipmentKeys) {
if (client[key] !== undefined) {
anyKnownKey = true;
replaceArray<IEquipmentDatabase>(db[key], client[key].map(convertEquipment));
}
}
if (client.WeaponSkins !== undefined) {
anyKnownKey = true;
replaceArray<IWeaponSkinDatabase>(db.WeaponSkins, client.WeaponSkins.map(convertWeaponSkin));
}
for (const key of ["Upgrades", "CrewShipSalvagedWeaponSkins", "CrewShipWeaponSkins"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
replaceArray<IUpgradeDatabase>(db[key], client[key].map(convertUpgrade));
}
}
@@ -280,6 +284,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"CrewShipRawSalvage"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key].splice(0, db[key].length);
client[key].forEach(x => {
db[key].push({
@@ -291,11 +296,13 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
}
for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
replaceArray<IOperatorConfigDatabase>(db[key], client[key].map(convertOperatorConfig));
}
}
for (const key of slotNames) {
if (client[key] !== undefined) {
anyKnownKey = true;
replaceSlots(db[key], client[key]);
}
}
@@ -312,6 +319,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"Counselor"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
@@ -338,6 +346,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"EchoesHexConquestCacheScoreMission"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
@@ -353,6 +362,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"ActiveAvatarImageType"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
@@ -369,6 +379,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"EchoesHexConquestActiveStickers"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
@@ -382,103 +393,133 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"EntratiVaultCountResetDate"
] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = fromMongoDate(client[key]);
}
}
// IRewardAtten[]
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
if (client.XPInfo !== undefined) {
anyKnownKey = true;
db.XPInfo = client.XPInfo;
}
if (client.CurrentLoadOutIds !== undefined) {
anyKnownKey = true;
db.CurrentLoadOutIds = client.CurrentLoadOutIds;
}
if (client.Affiliations !== undefined) {
anyKnownKey = true;
db.Affiliations = client.Affiliations;
}
if (client.FusionTreasures !== undefined) {
anyKnownKey = true;
db.FusionTreasures = client.FusionTreasures;
}
if (client.FocusUpgrades !== undefined) {
anyKnownKey = true;
db.FocusUpgrades = client.FocusUpgrades;
}
if (client.EvolutionProgress !== undefined) {
anyKnownKey = true;
db.EvolutionProgress = client.EvolutionProgress;
}
if (client.InfestedFoundry !== undefined) {
anyKnownKey = true;
db.InfestedFoundry = convertInfestedFoundry(client.InfestedFoundry);
}
if (client.DialogueHistory !== undefined) {
anyKnownKey = true;
db.DialogueHistory = convertDialogueHistory(client.DialogueHistory);
}
if (client.CustomMarkers !== undefined) {
anyKnownKey = true;
db.CustomMarkers = client.CustomMarkers;
}
if (client.ChallengeProgress !== undefined) {
anyKnownKey = true;
db.ChallengeProgress = client.ChallengeProgress;
}
if (client.QuestKeys !== undefined) {
anyKnownKey = true;
replaceArray<IQuestKeyDatabase>(db.QuestKeys, client.QuestKeys.map(convertQuestKey));
}
if (client.LastRegionPlayed !== undefined) {
anyKnownKey = true;
db.LastRegionPlayed = client.LastRegionPlayed;
}
if (client.PendingRecipes !== undefined) {
anyKnownKey = true;
replaceArray<IPendingRecipeDatabase>(db.PendingRecipes, client.PendingRecipes.map(convertPendingRecipe));
}
if (client.TauntHistory !== undefined) {
anyKnownKey = true;
db.TauntHistory = client.TauntHistory;
}
if (client.LoreFragmentScans !== undefined) {
anyKnownKey = true;
db.LoreFragmentScans = client.LoreFragmentScans;
}
for (const key of ["PendingSpectreLoadouts", "SpectreLoadouts"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
if (client.FocusXP !== undefined) {
anyKnownKey = true;
db.FocusXP = client.FocusXP;
}
for (const key of ["Alignment", "AlignmentReplay"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
if (client.StepSequencers !== undefined) {
anyKnownKey = true;
db.StepSequencers = client.StepSequencers;
}
if (client.CompletedJobChains !== undefined) {
anyKnownKey = true;
db.CompletedJobChains = client.CompletedJobChains;
}
if (client.Nemesis !== undefined) {
anyKnownKey = true;
db.Nemesis = convertNemesis(client.Nemesis);
}
if (client.PlayerSkills !== undefined) {
anyKnownKey = true;
db.PlayerSkills = client.PlayerSkills;
}
if (client.LotusCustomization !== undefined) {
anyKnownKey = true;
db.LotusCustomization = convertItemConfig(client.LotusCustomization);
}
if (client.CollectibleSeries !== undefined) {
anyKnownKey = true;
db.CollectibleSeries = client.CollectibleSeries;
}
for (const key of ["LibraryAvailableDailyTaskInfo", "LibraryActiveDailyTaskInfo"] as const) {
if (client[key] !== undefined) {
anyKnownKey = true;
db[key] = client[key];
}
}
if (client.SongChallenges !== undefined) {
anyKnownKey = true;
db.SongChallenges = client.SongChallenges;
}
if (client.Missions !== undefined) {
anyKnownKey = true;
db.Missions = client.Missions;
}
if (client.FlavourItems !== undefined) {
anyKnownKey = true;
db.FlavourItems.splice(0, db.FlavourItems.length);
client.FlavourItems.forEach(x => {
db.FlavourItems.push({
@@ -487,11 +528,14 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
});
}
if (client.Accolades !== undefined) {
anyKnownKey = true;
db.Accolades = client.Accolades;
}
if (client.Boosters !== undefined) {
anyKnownKey = true;
replaceArray<IBooster>(db.Boosters, client.Boosters);
}
return anyKnownKey;
};
export const importLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => {

View File

@@ -137,18 +137,19 @@ export const createInventory = async (
export const addStartingGear = async (
inventory: TInventoryDatabaseDocument,
startingGear?: TPartialStartingGear
startingGear?: Partial<TPartialStartingGear>
): Promise<IInventoryChanges> => {
if (inventory.ReceivedStartingGear) {
throw new Error(`account has already received starting gear`);
}
inventory.ReceivedStartingGear = true;
const { LongGuns, Pistols, Suits, Melee } = startingGear || {
const { LongGuns, Pistols, Suits, Melee } = {
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
Suits: [{ ItemType: "/Lotus/Powersuits/Excalibur/Excalibur" }],
Melee: [{ ItemType: "/Lotus/Weapons/Tenno/Melee/LongSword/LongSword" }]
Melee: [{ ItemType: "/Lotus/Weapons/Tenno/Melee/LongSword/LongSword" }],
...startingGear
};
//TODO: properly merge weapon bin changes it is currently static here

View File

@@ -1524,7 +1524,38 @@ export const addMissionRewards = async (
syndicateEntry = Goals.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!;
}
if (syndicateEntry && syndicateEntry.Jobs && !jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs")) {
const specialCase = [
{
endings: ["Heists/HeistProfitTakerBountyOne"],
stage: 2,
amount: 1000,
tag: "SolarisSyndicate"
},
{
endings: [
"Heists/HeistProfitTakerBountyTwo",
"Heists/HeistProfitTakerBountyThree",
"Heists/HeistProfitTakerBountyFour",
"Heists/HeistExploiterBountyOne"
],
amount: 1000,
tag: "SolarisSyndicate"
},
{ endings: ["Hunts/AllTeralystsHunt"], stage: 2, amount: 5000, tag: "CetusSyndicate" },
{
endings: ["Hunts/TeralystHunt"],
amount: 1000,
tag: "CetusSyndicate"
},
{ endings: ["Jobs/NewbieJob"], amount: 200, tag: "CetusSyndicate" }
];
const match = specialCase.find(rule => rule.endings.some(e => jobType.endsWith(e)));
if (match) {
const specialCaseReward = match.stage === undefined || rewardInfo.JobStage === match.stage ? match : null;
if (specialCaseReward) {
addStanding(inventory, match.tag, Math.floor(match.amount / (rewardInfo.Q ? 0.8 : 1)), AffiliationMods);
}
} else if (syndicateEntry && syndicateEntry.Jobs) {
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
if (
[
@@ -1582,42 +1613,15 @@ export const addMissionRewards = async (
);
logger.warning(`currentJob`, { currentJob: currentJob });
}
} else {
const specialCase = [
{ endings: ["Heists/HeistProfitTakerBountyOne"], stage: 2, amount: 1000 },
{ endings: ["Hunts/AllTeralystsHunt"], stage: 2, amount: 5000 },
{
endings: [
"Hunts/TeralystHunt",
"Heists/HeistProfitTakerBountyTwo",
"Heists/HeistProfitTakerBountyThree",
"Heists/HeistProfitTakerBountyFour",
"Heists/HeistExploiterBountyOne"
],
amount: 1000
}
];
const specialCaseReward = specialCase.find(
rule =>
rule.endings.some(e => jobType.endsWith(e)) &&
(rule.stage === undefined || rewardInfo.JobStage === rule.stage)
} else if (!jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs")) {
addStanding(
inventory,
syndicateEntry.Tag,
Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)),
AffiliationMods
);
if (specialCaseReward) {
addStanding(inventory, syndicateEntry.Tag, specialCaseReward.amount, AffiliationMods);
} else {
addStanding(
inventory,
syndicateEntry.Tag,
Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)),
AffiliationMods
);
}
}
}
if (jobType == "/Lotus/Types/Gameplay/Eidolon/Jobs/NewbieJob") {
addStanding(inventory, "CetusSyndicate", Math.floor(200 / (rewardInfo.Q ? 0.8 : 1)), AffiliationMods);
}
}
if (rewardInfo.challengeMissionId) {
@@ -1971,7 +1975,8 @@ function getRandomMissionDrops(
if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!;
}
if (syndicateEntry && syndicateEntry.Jobs) {
let job = syndicateEntry.Jobs[RewardInfo.JobTier!];
let job;
if (RewardInfo.JobTier && RewardInfo.JobTier > 0) job = syndicateEntry.Jobs[RewardInfo.JobTier];
if (syndicateEntry.Tag === "EntratiSyndicate") {
if (
@@ -2075,38 +2080,40 @@ function getRandomMissionDrops(
) {
job = syndicateEntry.Jobs.find(j => j.jobType === jobType)!;
}
rewardManifests = [job.rewards];
if (job.xpAmounts.length > 1) {
const curentStage = RewardInfo.JobStage! + 1;
const totalStage = job.xpAmounts.length;
let tableIndex = 1; // Stage 2, Stage 3 of 4, and Stage 3 of 5
if (job) {
rewardManifests = [job.rewards];
if (job.xpAmounts.length > 1) {
const curentStage = RewardInfo.JobStage! + 1;
const totalStage = job.xpAmounts.length;
let tableIndex = 1; // Stage 2, Stage 3 of 4, and Stage 3 of 5
if (curentStage == 1) {
tableIndex = 0;
} else if (curentStage == totalStage) {
tableIndex = 3;
} else if (totalStage == 5 && curentStage == 4) {
tableIndex = 2;
}
if (jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob")) {
if (RewardInfo.JobStage === job.xpAmounts.length - 1) {
rotations = [0, 1, 2];
if (curentStage == 1) {
tableIndex = 0;
} else if (curentStage == totalStage) {
tableIndex = 3;
} else if (totalStage == 5 && curentStage == 4) {
tableIndex = 2;
}
if (jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob")) {
if (RewardInfo.JobStage === job.xpAmounts.length - 1) {
rotations = [0, 1, 2];
} else {
rewardManifests = [];
}
} else {
rewardManifests = [];
rotations = [tableIndex];
}
} else {
rotations = [tableIndex];
rotations = [0];
}
if (
RewardInfo.Q &&
(RewardInfo.JobStage === job.xpAmounts.length - 1 || jobType.endsWith("VaultBounty")) &&
!jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob") &&
!isEndlessJob
) {
rotations.push(ExportRewards[job.rewards].length - 1);
}
} else {
rotations = [0];
}
if (
RewardInfo.Q &&
(RewardInfo.JobStage === job.xpAmounts.length - 1 || jobType.endsWith("VaultBounty")) &&
!jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob") &&
!isEndlessJob
) {
rotations.push(ExportRewards[job.rewards].length - 1);
}
}
}

View File

@@ -491,6 +491,7 @@ export const handleStoreItemAcquisition = async (
const slotPurchaseNameToSlotName: Record<string, { name: SlotNames; purchaseQuantity: number }> = {
SuitSlotItem: { name: "SuitBin", purchaseQuantity: 1 },
TwoSentinelSlotItem: { name: "SentinelBin", purchaseQuantity: 2 },
WeaponSlotItem: { name: "WeaponBin", purchaseQuantity: 1 },
TwoWeaponSlotItem: { name: "WeaponBin", purchaseQuantity: 2 },
SpaceSuitSlotItem: { name: "SpaceSuitBin", purchaseQuantity: 1 },
TwoSpaceWeaponSlotItem: { name: "SpaceWeaponBin", purchaseQuantity: 2 },
@@ -514,7 +515,9 @@ const handleSlotPurchase = (
): IPurchaseResponse => {
logger.debug(`slot name ${slotPurchaseNameFull}`);
const slotPurchaseName = slotPurchaseNameFull.substring(slotPurchaseNameFull.lastIndexOf("/") + 1);
if (!(slotPurchaseName in slotPurchaseNameToSlotName)) throw new Error(`invalid slot name ${slotPurchaseName}`);
if (!(slotPurchaseName in slotPurchaseNameToSlotName)) {
throw new Error(`invalid slot purchase name ${slotPurchaseName}`);
}
logger.debug(`slot purchase name ${slotPurchaseName}`);
const slotName = slotPurchaseNameToSlotName[slotPurchaseName].name;

View File

@@ -41,7 +41,7 @@ import type {
import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers.ts";
import { logger } from "../utils/logger.ts";
import { DailyDeal, Fissure } from "../models/worldStateModel.ts";
import { getConquest } from "./conquestService.ts";
import { factionToInt, getConquest, getMissionTypeForLegacyOverride } from "./conquestService.ts";
const sortieBosses = [
"SORTIE_BOSS_HYENA",
@@ -3562,13 +3562,11 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
worldState.KnownCalendarSeasons.push(getCalendarSeason(week + 1));
}
const season = (["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"] as const)[week % 4];
const labConquest = getConquest("CT_LAB", week, null);
const hexConquest = getConquest("CT_HEX", week, season);
if (!buildLabel || version_compare(buildLabel, "2025.10.14.16.10") >= 0) {
worldState.Conquests = [];
{
const season = (["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"] as const)[week % 4];
worldState.Conquests.push(getConquest("CT_LAB", week, null));
worldState.Conquests.push(getConquest("CT_HEX", week, season));
}
worldState.Conquests = [labConquest, hexConquest];
if (isBeforeNextExpectedWorldStateRefresh(timeMs, weekEnd)) {
const season = (["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"] as const)[(week + 1) % 4];
worldState.Conquests.push(getConquest("CT_LAB", week, null));
@@ -3621,6 +3619,19 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
e: cheeseEnd,
n: cheeseNext
},
lqo: {
mt: labConquest.Missions.map(x => getMissionTypeForLegacyOverride(x.missionType, "CT_LAB")),
mv: labConquest.Missions.map(x => x.difficulties[1].deviation),
c: labConquest.Missions.map(x => x.difficulties[1].risks),
fv: labConquest.Variables
},
hqo: {
mt: hexConquest.Missions.map(x => getMissionTypeForLegacyOverride(x.missionType, "CT_HEX")),
mv: hexConquest.Missions.map(x => x.difficulties[1].deviation),
mf: hexConquest.Missions.map(x => factionToInt(x.faction)),
c: hexConquest.Missions.map(x => x.difficulties[1].risks),
fv: hexConquest.Variables
},
sfn: [550, 553, 554, 555][halfHour % 4]
};
if (Array.isArray(config.worldState?.circuitGameModes)) {
@@ -3628,6 +3639,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}
worldState.Tmp = JSON.stringify(tmp);
// This must be the last field in these versions.
if (
buildLabel &&
version_compare(buildLabel, "2016.08.19.17.12") >= 0 &&
version_compare(buildLabel, "2017.05.05.15.41") <= 0
) {
worldState.WorldSeed = "4763605";
}
return worldState;
};

View File

@@ -92,6 +92,7 @@ interface IWsMsgToClient {
// to game/bootstrapper (https://openwf.io/bootstrapper-manual)
sync_inventory?: boolean;
sync_world_state?: boolean;
tunables?: ITunables;
}

View File

@@ -2,6 +2,7 @@ import type { IMissionReward, TFaction, TMissionType } from "warframe-public-exp
import type { IMongoDate, IOid } from "./commonTypes.ts";
export interface IWorldState {
WorldSeed?: string;
Version: number; // for goals
BuildLabel: string;
Time: number;
@@ -485,9 +486,9 @@ interface IFbst {
// < 40.0.0
interface IConquestOverride {
mt?: string[]; // mission types but "Exterminate" instead of "MT_EXTERMINATION", etc. and "DualDefense" instead of "Defense" for hex conquest
mt?: string[];
mv?: string[];
mf?: number[]; // hex conquest only
mf?: number[];
c?: [string, string][];
fv?: string[];
}

View File

@@ -840,6 +840,10 @@
</form>
</div>
</div>
<div id="equipmentFeatures-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_equipmentFeaturesLabel"></h5>
<div id="equipmentFeaturesButtons-card" class="card-body d-flex flex-wrap gap-2"></div>
</div>
</div>
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
<p class="mb-3 inventory-update-note"></p>

View File

@@ -734,7 +734,7 @@ const accountCheats = document.querySelectorAll("#account-cheats input[id]");
// Assumes that caller revalidates authz
function updateInventory() {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1&ignoreBuildLabel=1");
req.done(data => {
window.itemListPromise.then(itemMap => {
window.didInitialInventoryUpdate = true;
@@ -912,12 +912,7 @@ function updateInventory() {
td.appendChild(a);
}
if (
["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(
category
) ||
modularWeapons.includes(item.ItemType)
) {
{
const a = document.createElement("a");
a.href =
"/webui/detailedView?productCategory=" + category + "&itemId=" + item.ItemId.$oid;
@@ -930,7 +925,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
gildEquipment(category, item.ItemId.$oid);
equipmentFeatures(category, item.ItemId.$oid, 8);
};
a.title = loc("code_gild");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"/></svg>`;
@@ -1560,6 +1555,57 @@ function updateInventory() {
$("#detailedView-title").text(itemName);
}
{
document.getElementById("equipmentFeatures-card").classList.remove("d-none");
const buttonsCard = document.getElementById("equipmentFeaturesButtons-card");
buttonsCard.innerHTML = "";
item.Features ??= 0;
const bits = [];
if (category != "OperatorAmps") bits.push(1);
if (["Suits", "LongGuns", "Pistols", "Melee"].includes(category)) bits.push(2);
if (modularWeapons.includes(item.ItemType)) bits.push(8);
if (["LongGuns", "Pistols", "Melee", "SpaceGuns", "OperatorAmps"].includes(category))
bits.push(32);
if (category == "SpaceGuns") bits.push(4, 64);
if (
["LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category) &&
item.UpgradeFingerprint
)
bits.push(1024);
for (const bit of bits.sort((a, b) => a - b)) {
const wrapper = document.createElement("div");
wrapper.classList = "form-check";
const input = document.createElement("input");
input.classList = "form-check-input";
input.type = "checkbox";
input.id = `detailedView-feature-${bit}`;
input.checked = item.Features & bit;
const label = document.createElement("label");
label.classList = "form-check-label";
label.htmlFor = input.id;
label.innerHTML = loc(`code_feature_${bit}`);
label.setAttribute("data-loc", `code_feature_${bit}`);
input.onchange = function (event) {
event.preventDefault();
equipmentFeatures(category, oid, bit);
};
if (
(data.unlockDoubleCapacityPotatoesEverywhere && bit === 1) ||
(data.unlockExilusEverywhere && bit === 2) ||
(data.unlockArcanesEverywhere && (bit === 32 || bit === 64))
) {
input.disabled = true;
}
wrapper.appendChild(input);
wrapper.appendChild(label);
buttonsCard.appendChild(wrapper);
}
}
if (category == "Suits") {
document.getElementById("archonShards-card").classList.remove("d-none");
@@ -2673,7 +2719,7 @@ function addMissingEvolutionProgress() {
function maxRankAllEvolutions() {
revalidateAuthz().then(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1&ignoreBuildLabel=1");
req.done(data => {
const requests = [];
@@ -2697,7 +2743,7 @@ function maxRankAllEvolutions() {
function maxRankAllEquipment(categories) {
revalidateAuthz().then(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1&ignoreBuildLabel=1");
req.done(data => {
window.itemListPromise.then(itemMap => {
const batchData = {};
@@ -2864,15 +2910,11 @@ function disposeOfItems(category, type, count) {
});
}
function gildEquipment(category, oid) {
function equipmentFeatures(category, oid, bit) {
revalidateAuthz().then(() => {
$.post({
url: "/api/gildWeapon.php?" + window.authz + "&ItemId=" + oid + "&Category=" + category,
contentType: "application/octet-stream",
data: JSON.stringify({
Recipe: "webui"
})
}).done(function () {
$.get(
"/custom/equipmentFeatures?" + window.authz + "&ItemId=" + oid + "&Category=" + category + "&bit=" + bit
).done(function () {
updateInventory();
});
});
@@ -3035,7 +3077,7 @@ function doAcquireRiven() {
])
}).done(function () {
// Get riven's assigned id
$.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(data => {
$.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1&ignoreBuildLabel=1").done(data => {
for (const rawUpgrade of data.RawUpgrades) {
if (rawUpgrade.ItemType === uniqueName) {
// Add fingerprint to riven
@@ -3259,33 +3301,35 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
function doUnlockAllFocusSchools() {
revalidateAuthz().then(() => {
$.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(async data => {
const missingFocusUpgrades = {
"/Lotus/Upgrades/Focus/Attack/AttackFocusAbility": true,
"/Lotus/Upgrades/Focus/Tactic/TacticFocusAbility": true,
"/Lotus/Upgrades/Focus/Ward/WardFocusAbility": true,
"/Lotus/Upgrades/Focus/Defense/DefenseFocusAbility": true,
"/Lotus/Upgrades/Focus/Power/PowerFocusAbility": true
};
if (data.FocusUpgrades) {
for (const focusUpgrade of data.FocusUpgrades) {
if (focusUpgrade.ItemType in missingFocusUpgrades) {
delete missingFocusUpgrades[focusUpgrade.ItemType];
$.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1&ignoreBuildLabel=1").done(
async data => {
const missingFocusUpgrades = {
"/Lotus/Upgrades/Focus/Attack/AttackFocusAbility": true,
"/Lotus/Upgrades/Focus/Tactic/TacticFocusAbility": true,
"/Lotus/Upgrades/Focus/Ward/WardFocusAbility": true,
"/Lotus/Upgrades/Focus/Defense/DefenseFocusAbility": true,
"/Lotus/Upgrades/Focus/Power/PowerFocusAbility": true
};
if (data.FocusUpgrades) {
for (const focusUpgrade of data.FocusUpgrades) {
if (focusUpgrade.ItemType in missingFocusUpgrades) {
delete missingFocusUpgrades[focusUpgrade.ItemType];
}
}
}
for (const upgradeType of Object.keys(missingFocusUpgrades)) {
await unlockFocusSchool(upgradeType);
}
if (Object.keys(missingFocusUpgrades).length == 0) {
toast(loc("code_focusAllUnlocked"));
} else {
toast(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length));
if (ws_is_open) {
window.ws.send(JSON.stringify({ sync_inventory: true }));
}
}
}
for (const upgradeType of Object.keys(missingFocusUpgrades)) {
await unlockFocusSchool(upgradeType);
}
if (Object.keys(missingFocusUpgrades).length == 0) {
toast(loc("code_focusAllUnlocked"));
} else {
toast(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length));
if (ws_is_open) {
window.ws.send(JSON.stringify({ sync_inventory: true }));
}
}
});
);
});
}
@@ -3403,7 +3447,7 @@ function doAddAllMods() {
modsAll.delete("/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser");
revalidateAuthz().then(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1&ignoreBuildLabel=1");
req.done(data => {
for (const modOwned of data.RawUpgrades) {
if ((modOwned.ItemCount ?? 1) > 0) {
@@ -3435,7 +3479,7 @@ function doAddAllMods() {
function doRemoveUnrankedMods() {
revalidateAuthz().then(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1&ignoreBuildLabel=1");
req.done(inventory => {
window.itemListPromise.then(itemMap => {
$.post({
@@ -3478,6 +3522,8 @@ single.getRoute("#detailedView-route").on("beforeload", function () {
document.getElementById("modularParts-card").classList.add("d-none");
document.getElementById("modularParts-form").innerHTML = "";
document.getElementById("valenceBonus-card").classList.add("d-none");
document.getElementById("equipmentFeatures-card").classList.add("d-none");
document.getElementById("equipmentFeaturesButtons-card").innerHTML = "";
if (window.didInitialInventoryUpdate) {
updateInventory();
}
@@ -3552,16 +3598,21 @@ function doPopArchonCrystalUpgrade(type) {
function doImport() {
revalidateAuthz().then(() => {
$.post({
url: "/custom/import?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
inventory: JSON.parse($("#import-inventory").val())
})
}).then(function () {
toast(loc("code_succImport"));
updateInventory();
});
try {
$.post({
url: "/custom/import?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
inventory: JSON.parse($("#import-inventory").val())
})
}).then(function (anyKnownKey) {
toast(loc(anyKnownKey ? "code_succImport" : "code_nothingToDo"));
updateInventory();
});
} catch (e) {
toast(e);
console.error(e);
}
});
}

View File

@@ -81,6 +81,13 @@ dict = {
code_operatorFaceName: `Operator-Gesicht: |INDEX|`,
code_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `Du musst sowohl ein Offensiv- als auch ein Support-Upgrade auswählen.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
code_feature_4: `[UNTRANSLATED] Gravimag`,
code_feature_8: `[UNTRANSLATED] Gild`,
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
code_feature_1024: `[UNTRANSLATED] Valence Override`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
login_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`,
@@ -155,6 +162,7 @@ dict = {
detailedView_modularPartsLabel: `Modulare Teile ändern`,
detailedView_invigorationLabel: `Kräftigung`,
detailedView_loadoutLabel: `Loadouts`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% Fähigkeitsstärke`,
invigorations_offensive_AbilityRange: `+100% Fähigkeitsreichweite`,

View File

@@ -80,6 +80,13 @@ dict = {
code_operatorFaceName: `Operator Visage |INDEX|`,
code_succChange: `Successfully changed.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`,
code_feature_1: `Orokin Reactor`,
code_feature_2: `Exilus Adapter`,
code_feature_4: `Gravimag`,
code_feature_8: `Gild`,
code_feature_32: `Arcane Slot`,
code_feature_64: `Second Arcane Slot`,
code_feature_1024: `Valence Override`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
login_emailLabel: `Email address`,
login_passwordLabel: `Password`,
@@ -154,6 +161,7 @@ dict = {
detailedView_modularPartsLabel: `Change Modular Parts`,
detailedView_invigorationLabel: `Invigoration`,
detailedView_loadoutLabel: `Loadouts`,
detailedView_equipmentFeaturesLabel: `Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% Ability Strength`,
invigorations_offensive_AbilityRange: `+100% Ability Range`,

View File

@@ -81,6 +81,13 @@ dict = {
code_operatorFaceName: `Rostro del operador |INDEX|`,
code_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
code_feature_4: `[UNTRANSLATED] Gravimag`,
code_feature_8: `[UNTRANSLATED] Gild`,
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
code_feature_1024: `[UNTRANSLATED] Valence Override`,
login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
login_emailLabel: `Dirección de correo electrónico`,
login_passwordLabel: `Contraseña`,
@@ -155,6 +162,7 @@ dict = {
detailedView_modularPartsLabel: `Cambiar partes modulares`,
detailedView_invigorationLabel: `Fortalecimiento`,
detailedView_loadoutLabel: `Equipamientos`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% Fuerza de Habilidad`,
invigorations_offensive_AbilityRange: `+100% Alcance de Habilidad`,

View File

@@ -81,6 +81,13 @@ dict = {
code_operatorFaceName: `Visage de l'Opérateur |INDEX|`,
code_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `Invigoration offensive et défensive requises.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
code_feature_4: `[UNTRANSLATED] Gravimag`,
code_feature_8: `[UNTRANSLATED] Gild`,
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
code_feature_1024: `[UNTRANSLATED] Valence Override`,
login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`,
@@ -155,6 +162,7 @@ dict = {
detailedView_modularPartsLabel: `Changer l'équipement modulaire`,
detailedView_invigorationLabel: `Dynamisation`,
detailedView_loadoutLabel: `Équipements`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% de puissance de pouvoir`,
invigorations_offensive_AbilityRange: `+100% de portée de pouvoir`,

View File

@@ -81,6 +81,13 @@ dict = {
code_operatorFaceName: `Внешность оператора: |INDEX|`,
code_succChange: `Успешно изменено.`,
code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
code_feature_1: `Реактор Орокин`,
code_feature_2: `Адаптер Эксилус`,
code_feature_4: `Гравимаг`,
code_feature_8: `Улучшение`,
code_feature_32: `Слот Мистификатора`,
code_feature_64: `Второй слот Мистификатора`,
code_feature_1024: `Переопределение валентности`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
login_emailLabel: `Адрес электронной почты`,
login_passwordLabel: `Пароль`,
@@ -155,6 +162,7 @@ dict = {
detailedView_modularPartsLabel: `Изменить модульные части`,
detailedView_invigorationLabel: `Воодушевление`,
detailedView_loadoutLabel: `Конфигурации`,
detailedView_equipmentFeaturesLabel: `Модификаторы снаряжения`,
invigorations_offensive_AbilityStrength: `+200% к силе способностей.`,
invigorations_offensive_AbilityRange: `+100% к зоне поражения способностей.`,

View File

@@ -81,6 +81,13 @@ dict = {
code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
code_succChange: `Успішно змінено.`,
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
code_feature_4: `[UNTRANSLATED] Gravimag`,
code_feature_8: `[UNTRANSLATED] Gild`,
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
code_feature_1024: `[UNTRANSLATED] Valence Override`,
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
login_emailLabel: `Адреса електронної пошти`,
login_passwordLabel: `Пароль`,
@@ -155,6 +162,7 @@ dict = {
detailedView_modularPartsLabel: `Змінити модульні частини`,
detailedView_invigorationLabel: `Зміцнення`,
detailedView_loadoutLabel: `Конфігурації`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200% до потужності здібностей.`,
invigorations_offensive_AbilityRange: `+100% до досяжності здібностей.`,

View File

@@ -81,6 +81,13 @@ dict = {
code_operatorFaceName: `指挥官面部 |INDEX|`,
code_succChange: `更改成功`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
code_feature_1: `[UNTRANSLATED] Orokin Reactor`,
code_feature_2: `[UNTRANSLATED] Exilus Adapter`,
code_feature_4: `[UNTRANSLATED] Gravimag`,
code_feature_8: `[UNTRANSLATED] Gild`,
code_feature_32: `[UNTRANSLATED] Arcane Slot`,
code_feature_64: `[UNTRANSLATED] Second Arcane Slot`,
code_feature_1024: `[UNTRANSLATED] Valence Override`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`,
@@ -155,6 +162,7 @@ dict = {
detailedView_modularPartsLabel: `更换部件`,
detailedView_invigorationLabel: `活化`,
detailedView_loadoutLabel: `配置`,
detailedView_equipmentFeaturesLabel: `[UNTRANSLATED] Equipment Features`,
invigorations_offensive_AbilityStrength: `+200%技能强度`,
invigorations_offensive_AbilityRange: `+100%技能范围`,