Compare commits

...

17 Commits

Author SHA1 Message Date
267357871b feat: handle HenchmenKilled & HintProgress incrementing (#1877)
Closes #1807

Reviewed-on: OpenWF/SpaceNinjaServer#1877
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 15:25:47 -07:00
cf5ed0442d fix: don't assume rewardInfo.node is in ExportRegions (#1879)
Fixes #1878

Reviewed-on: OpenWF/SpaceNinjaServer#1879
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 14:23:00 -07:00
de36e2ee8d fix: close connection for dating saveDialogue request (#1873)
Missing fix for #1852

Reviewed-on: OpenWF/SpaceNinjaServer#1873
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 12:14:42 -07:00
ca1b6c31b6 fix: give rewards for completing a capture mission (#1872)
Reviewed-on: OpenWF/SpaceNinjaServer#1872
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:57:03 -07:00
d66c474bfc fix: some issues with sortie generation (#1871)
Now using sortieTilesets as a source of truth for allowed mission nodes as it's based only on real sorties, also added disallowed mission types for FC_OROKIN (Corrupted Vor) that otherwise cause a script error.

Closes #1865

Reviewed-on: OpenWF/SpaceNinjaServer#1871
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:56:41 -07:00
781f01520f feat: save lotus customization (#1864)
Closes #768

Reviewed-on: OpenWF/SpaceNinjaServer#1864
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:56:22 -07:00
ac37702468 feat(webui): add missing max rank mods (#1863)
Closes #916

Reviewed-on: OpenWF/SpaceNinjaServer#1863
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:56:16 -07:00
75c011e3cb fix: don't set IsNew flag for starting gear (#1859)
Reviewed-on: OpenWF/SpaceNinjaServer#1859
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:56:06 -07:00
4d4f885c8e feat: dontSubtractConsumables cheat (#1857)
Closes #1838

Reviewed-on: OpenWF/SpaceNinjaServer#1857
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:55:45 -07:00
66d1a65e63 fix: handle credits & platinum prices from vendors (#1856)
Fixes #1837

Reviewed-on: OpenWF/SpaceNinjaServer#1856
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:55:03 -07:00
48eefd8db1 fix: don't give droptable rewards for non-assassination sortie missions (#1855)
Closes #1835

Reviewed-on: OpenWF/SpaceNinjaServer#1855
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:54:54 -07:00
4a6a5ea9cc feat: handle WeaponSkins picked up in missions (#1854)
For sigils.

Closes #1839

Reviewed-on: OpenWF/SpaceNinjaServer#1854
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:54:38 -07:00
95c0ad7892 fix: handle saveDialogue request without Data or Gift (#1853)
Needed to just set booleans when starting dating.

Closes #1852

Reviewed-on: OpenWF/SpaceNinjaServer#1853
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:54:25 -07:00
a90d3a5156 feat: gardening (#1849)
Reviewed-on: OpenWF/SpaceNinjaServer#1849
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:54:06 -07:00
d0c9409a2d fix: exclude pvp variants from daily special parts (#1846)
Fixes #1836

Reviewed-on: OpenWF/SpaceNinjaServer#1846
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:53:41 -07:00
bbde7b2141 chore: don't change remote/origin url 2025-04-26 08:12:54 +02:00
5271123090 chore(webui): update to Spanish translation (#1862)
Reviewed-on: OpenWF/SpaceNinjaServer#1862
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-04-25 22:13:28 -07:00
37 changed files with 546 additions and 175 deletions

View File

@ -1,7 +1,6 @@
@echo off @echo off
echo Updating SpaceNinjaServer... echo Updating SpaceNinjaServer...
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
git fetch --prune git fetch --prune
git stash git stash
git reset --hard origin/main git reset --hard origin/main

View File

@ -19,6 +19,7 @@
"infiniteEndo": false, "infiniteEndo": false,
"infiniteRegalAya": false, "infiniteRegalAya": false,
"infiniteHelminthMaterials": false, "infiniteHelminthMaterials": false,
"dontSubtractConsumables": false,
"unlockAllShipFeatures": false, "unlockAllShipFeatures": false,
"unlockAllShipDecorations": false, "unlockAllShipDecorations": false,
"unlockAllFlavourItems": false, "unlockAllFlavourItems": false,

View File

@ -62,14 +62,7 @@ export const crewShipIdentifySalvageController: RequestHandler = async (req, res
} satisfies IInnateDamageFingerprint) } satisfies IInnateDamageFingerprint)
}; };
} }
addEquipment( addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, defaultOverwrites, inventoryChanges);
inventory,
"CrewShipSalvagedWeapons",
payload.ItemType,
undefined,
inventoryChanges,
defaultOverwrites
);
} }
inventoryChanges.CrewShipRawSalvage = [ inventoryChanges.CrewShipRawSalvage = [

View File

@ -104,13 +104,14 @@ export const focusController: RequestHandler = async (req, res) => {
} }
case FocusOperation.SentTrainingAmplifier: { case FocusOperation.SentTrainingAmplifier: {
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest; const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
const parts: string[] = [ const inventory = await getInventory(accountId);
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
ModularParts: [
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel" "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
]; ]
const inventory = await getInventory(accountId); });
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
occupySlot(inventory, InventorySlot.AMPS, false); occupySlot(inventory, InventorySlot.AMPS, false);
await inventory.save(); await inventory.save();
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]); res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);

View File

@ -0,0 +1,84 @@
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
import { toStoreItem } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
import { IMongoDate } from "@/src/types/commonTypes";
import { IMissionReward } from "@/src/types/missionTypes";
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IGardeningClient } from "@/src/types/shipTypes";
import { RequestHandler } from "express";
import { dict_en, ExportResources } from "warframe-public-export-plus";
export const gardeningController: RequestHandler = async (req, res) => {
const data = getJSONfromString<IGardeningRequest>(String(req.body));
if (data.Mode != "HarvestAll") {
throw new Error(`unexpected gardening mode: ${data.Mode}`);
}
const accountId = await getAccountIdForRequest(req);
const [inventory, personalRooms] = await Promise.all([
getInventory(accountId, "MiscItems"),
getPersonalRooms(accountId, "Apartment")
]);
// Harvest plants
const inventoryChanges: IInventoryChanges = {};
const rewards: Record<string, IMissionReward[][]> = {};
for (const planter of personalRooms.Apartment.Gardening.Planters) {
rewards[planter.Name] = [];
for (const plant of planter.Plants) {
const itemType =
"/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItem" +
plant.PlantType.substring(plant.PlantType.length - 1);
const itemCount = Math.random() < 0.775 ? 2 : 4;
addMiscItem(inventory, itemType, itemCount, inventoryChanges);
rewards[planter.Name].push([
{
StoreItem: toStoreItem(itemType),
TypeName: itemType,
ItemCount: itemCount,
DailyCooldown: false,
Rarity: itemCount == 2 ? 0.7743589743589744 : 0.22564102564102564,
TweetText: `${itemCount}x ${dict_en[ExportResources[itemType].name]} (Resource)`,
ProductCategory: "MiscItems"
}
]);
}
}
// Refresh garden
personalRooms.Apartment.Gardening = createGarden();
await Promise.all([inventory.save(), personalRooms.save()]);
const planter = personalRooms.Apartment.Gardening.Planters[personalRooms.Apartment.Gardening.Planters.length - 1];
const plant = planter.Plants[planter.Plants.length - 1];
res.json({
GardenTagName: planter.Name,
PlantType: plant.PlantType,
PlotIndex: plant.PlotIndex,
EndTime: toMongoDate(plant.EndTime),
InventoryChanges: inventoryChanges,
Gardening: personalRooms.toJSON<IPersonalRoomsClient>().Apartment.Gardening,
Rewards: rewards
} satisfies IGardeningResponse);
};
interface IGardeningRequest {
Mode: string;
}
interface IGardeningResponse {
GardenTagName: string;
PlantType: string;
PlotIndex: number;
EndTime: IMongoDate;
InventoryChanges: IInventoryChanges;
Gardening: IGardeningClient;
Rewards: Record<string, IMissionReward[][]>;
}

View File

@ -2,17 +2,24 @@ import { RequestHandler } from "express";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json"; import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
import { getShip } from "@/src/services/shipService"; import { getShip } from "@/src/services/shipService";
import { toOid } from "@/src/helpers/inventoryHelpers"; import { toOid } from "@/src/helpers/inventoryHelpers";
import { IGetShipResponse } from "@/src/types/shipTypes"; import { IGetShipResponse } from "@/src/types/shipTypes";
import { IPersonalRooms } from "@/src/types/personalRoomsTypes"; import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { getLoadout } from "@/src/services/loadoutService"; import { getLoadout } from "@/src/services/loadoutService";
export const getShipController: RequestHandler = async (req, res) => { export const getShipController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const personalRoomsDb = await getPersonalRooms(accountId); const personalRoomsDb = await getPersonalRooms(accountId);
const personalRooms = personalRoomsDb.toJSON<IPersonalRooms>();
// Setup gardening if it's missing. Maybe should be done as part of some quest completion in the future.
if (personalRoomsDb.Apartment.Gardening.Planters.length == 0) {
personalRoomsDb.Apartment.Gardening = createGarden();
await personalRoomsDb.save();
}
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
const loadout = await getLoadout(accountId); const loadout = await getLoadout(accountId);
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem"); const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");

View File

@ -441,16 +441,9 @@ const finishComponentRepair = (
const inventoryChanges = { const inventoryChanges = {
...(category == "CrewShipWeaponSkins" ...(category == "CrewShipWeaponSkins"
? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint) ? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
: addEquipment( : addEquipment(inventory, category, salvageItem.ItemType, {
inventory,
category,
salvageItem.ItemType,
undefined,
{},
{
UpgradeFingerprint: salvageItem.UpgradeFingerprint UpgradeFingerprint: salvageItem.UpgradeFingerprint
} })),
)),
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false) ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
}; };

View File

@ -36,7 +36,9 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
let defaultUpgrades: IDefaultUpgrade[] | undefined; let defaultUpgrades: IDefaultUpgrade[] | undefined;
const defaultOverwrites: Partial<IEquipmentDatabase> = {}; const defaultOverwrites: Partial<IEquipmentDatabase> = {
ModularParts: data.Parts
};
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
if (category == "KubrowPets") { if (category == "KubrowPets") {
const traits = { const traits = {
@ -151,7 +153,7 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
} }
} }
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades); defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites); addEquipment(inventory, category, data.WeaponType, defaultOverwrites, inventoryChanges);
combineInventoryChanges( combineInventoryChanges(
inventoryChanges, inventoryChanges,
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi) occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)

View File

@ -21,7 +21,11 @@ import { IInventoryChanges } from "@/src/types/purchaseTypes";
export const modularWeaponSaleController: RequestHandler = async (req, res) => { export const modularWeaponSaleController: RequestHandler = async (req, res) => {
const partTypeToParts: Record<string, string[]> = {}; const partTypeToParts: Record<string, string[]> = {};
for (const [uniqueName, data] of Object.entries(ExportWeapons)) { for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
if (data.partType && data.premiumPrice) { if (
data.partType &&
data.premiumPrice &&
!data.excludeFromCodex // exclude pvp variants
) {
partTypeToParts[data.partType] ??= []; partTypeToParts[data.partType] ??= [];
partTypeToParts[data.partType].push(uniqueName); partTypeToParts[data.partType].push(uniqueName);
} }
@ -41,24 +45,18 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts); const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts);
const configs = applyDefaultUpgrades(inventory, defaultUpgrades); const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
const inventoryChanges: IInventoryChanges = { const inventoryChanges: IInventoryChanges = {
...addEquipment( ...addEquipment(inventory, category, weaponInfo.ItemType, {
inventory,
category,
weaponInfo.ItemType,
weaponInfo.ModularParts,
{},
{
Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED, Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED,
ItemName: payload.ItemName, ItemName: payload.ItemName,
Configs: configs, Configs: configs,
ModularParts: weaponInfo.ModularParts,
Polarity: [ Polarity: [
{ {
Slot: payload.PolarizeSlot, Slot: payload.PolarizeSlot,
Value: payload.PolarizeValue Value: payload.PolarizeValue
} }
] ]
} }),
),
...occupySlot(inventory, productCategoryToInventoryBin(category)!, true), ...occupySlot(inventory, productCategoryToInventoryBin(category)!, true),
...updateCurrency(inventory, weaponInfo.PremiumPrice, true) ...updateCurrency(inventory, weaponInfo.PremiumPrice, true)
}; };

View File

@ -4,7 +4,6 @@ import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inven
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes"; import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const saveDialogueController: RequestHandler = async (req, res) => { export const saveDialogueController: RequestHandler = async (req, res) => {
@ -27,7 +26,6 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
const dialogue = getDialogue(inventory, request.DialogueName); const dialogue = getDialogue(inventory, request.DialogueName);
dialogue.Rank = request.Rank; dialogue.Rank = request.Rank;
dialogue.Chemistry = request.Chemistry; dialogue.Chemistry = request.Chemistry;
if (request.Data) {
dialogue.QueuedDialogues = request.QueuedDialogues; dialogue.QueuedDialogues = request.QueuedDialogues;
for (const bool of request.Booleans) { for (const bool of request.Booleans) {
dialogue.Booleans.push(bool); dialogue.Booleans.push(bool);
@ -45,8 +43,6 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
dialogue.Booleans.splice(index, 1); dialogue.Booleans.splice(index, 1);
} }
} }
dialogue.Completed.push(request.Data);
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
for (const info of request.OtherDialogueInfos) { for (const info of request.OtherDialogueInfos) {
const otherDialogue = getDialogue(inventory, info.Dialogue); const otherDialogue = getDialogue(inventory, info.Dialogue);
if (info.Tag != "") { if (info.Tag != "") {
@ -54,6 +50,9 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
} }
otherDialogue.Chemistry += info.Value; // unsure otherDialogue.Chemistry += info.Value; // unsure
} }
if (request.Data) {
dialogue.Completed.push(request.Data);
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
await inventory.save(); await inventory.save();
res.json({ res.json({
InventoryChanges: inventoryChanges, InventoryChanges: inventoryChanges,
@ -74,7 +73,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
AvailableGiftDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } } AvailableGiftDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
}); });
} else { } else {
logger.error(`saveDialogue request not fully handled: ${String(req.body)}`); res.end();
} }
} }
}; };

View File

@ -0,0 +1,44 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { ExportArcanes, ExportUpgrades } from "warframe-public-export-plus";
export const addMissingMaxRankModsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "Upgrades");
const maxOwnedRanks: Record<string, number> = {};
for (const upgrade of inventory.Upgrades) {
const fingerprint = JSON.parse(upgrade.UpgradeFingerprint ?? "{}") as { lvl?: number };
if (fingerprint.lvl) {
maxOwnedRanks[upgrade.ItemType] ??= 0;
if (fingerprint.lvl > maxOwnedRanks[upgrade.ItemType]) {
maxOwnedRanks[upgrade.ItemType] = fingerprint.lvl;
}
}
}
for (const [uniqueName, data] of Object.entries(ExportUpgrades)) {
if (data.fusionLimit != 0 && data.type != "PARAZON" && maxOwnedRanks[uniqueName] != data.fusionLimit) {
inventory.Upgrades.push({
ItemType: uniqueName,
UpgradeFingerprint: JSON.stringify({ lvl: data.fusionLimit })
});
}
}
for (const [uniqueName, data] of Object.entries(ExportArcanes)) {
if (
data.name != "/Lotus/Language/Items/GenericCosmeticEnhancerName" &&
maxOwnedRanks[uniqueName] != data.fusionLimit
) {
inventory.Upgrades.push({
ItemType: uniqueName,
UpgradeFingerprint: JSON.stringify({ lvl: data.fusionLimit })
});
}
}
await inventory.save();
res.end();
};

View File

@ -96,7 +96,8 @@ import {
IInvasionProgressDatabase, IInvasionProgressDatabase,
IInvasionProgressClient, IInvasionProgressClient,
IAccolades, IAccolades,
IHubNpcCustomization IHubNpcCustomization,
ILotusCustomization
} from "../../types/inventoryTypes/inventoryTypes"; } from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes"; import { IOid } from "../../types/commonTypes";
import { import {
@ -780,6 +781,10 @@ const loreFragmentScansSchema = new Schema<ILoreFragmentScan>(
{ _id: false } { _id: false }
); );
const lotusCustomizationSchema = new Schema<ILotusCustomization>().add(ItemConfigSchema).add({
Persona: String
});
const evolutionProgressSchema = new Schema<IEvolutionProgress>( const evolutionProgressSchema = new Schema<IEvolutionProgress>(
{ {
Progress: Number, Progress: Number,
@ -1628,7 +1633,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//Purchase this new permanent skin from the Lotus customization options in Personal Quarters located in your Orbiter. //Purchase this new permanent skin from the Lotus customization options in Personal Quarters located in your Orbiter.
//https://warframe.fandom.com/wiki/Lotus#The_New_War //https://warframe.fandom.com/wiki/Lotus#The_New_War
LotusCustomization: Schema.Types.Mixed, LotusCustomization: { type: lotusCustomizationSchema, default: undefined },
//Progress+Rank+ItemType(ZarimanPumpShotgun) //Progress+Rank+ItemType(ZarimanPumpShotgun)
//https://warframe.fandom.com/wiki/Incarnon //https://warframe.fandom.com/wiki/Incarnon

View File

@ -1,14 +1,17 @@
import { toOid } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { colorSchema } from "@/src/models/inventoryModels/inventoryModel"; import { colorSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IOrbiter, IPersonalRoomsDatabase, PersonalRoomsModelType } from "@/src/types/personalRoomsTypes"; import { IOrbiter, IPersonalRoomsDatabase, PersonalRoomsModelType } from "@/src/types/personalRoomsTypes";
import { import {
IFavouriteLoadoutDatabase, IFavouriteLoadoutDatabase,
IGardening, IGardeningDatabase,
IPlacedDecosDatabase, IPlacedDecosDatabase,
IPictureFrameInfo, IPictureFrameInfo,
IRoom, IRoom,
ITailorShopDatabase, ITailorShopDatabase,
IApartmentDatabase IApartmentDatabase,
IPlanterDatabase,
IPlantDatabase,
IPlantClient
} from "@/src/types/shipTypes"; } from "@/src/types/shipTypes";
import { Schema, model } from "mongoose"; import { Schema, model } from "mongoose";
@ -77,15 +80,45 @@ favouriteLoadoutSchema.set("toJSON", {
} }
}); });
const gardeningSchema = new Schema<IGardening>({ const plantSchema = new Schema<IPlantDatabase>(
Planters: [Schema.Types.Mixed] //TODO: add when implementing gardening {
PlantType: String,
EndTime: Date,
PlotIndex: Number
},
{ _id: false }
);
plantSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
const client = obj as IPlantClient;
const db = obj as IPlantDatabase;
client.EndTime = toMongoDate(db.EndTime);
}
}); });
const planterSchema = new Schema<IPlanterDatabase>(
{
Name: { type: String, required: true },
Plants: { type: [plantSchema], default: [] }
},
{ _id: false }
);
const gardeningSchema = new Schema<IGardeningDatabase>(
{
Planters: { type: [planterSchema], default: [] }
},
{ _id: false }
);
const apartmentSchema = new Schema<IApartmentDatabase>( const apartmentSchema = new Schema<IApartmentDatabase>(
{ {
Rooms: [roomSchema], Rooms: [roomSchema],
FavouriteLoadouts: [favouriteLoadoutSchema], FavouriteLoadouts: [favouriteLoadoutSchema],
Gardening: gardeningSchema // TODO: ensure this is correct Gardening: gardeningSchema
}, },
{ _id: false } { _id: false }
); );
@ -98,7 +131,9 @@ const apartmentDefault: IApartmentDatabase = {
{ Name: "DuviriHallway", MaxCapacity: 1600 } { Name: "DuviriHallway", MaxCapacity: 1600 }
], ],
FavouriteLoadouts: [], FavouriteLoadouts: [],
Gardening: {} Gardening: {
Planters: []
}
}; };
const orbiterSchema = new Schema<IOrbiter>( const orbiterSchema = new Schema<IOrbiter>(

View File

@ -48,6 +48,7 @@ import { findSessionsController } from "@/src/controllers/api/findSessionsContro
import { fishmongerController } from "@/src/controllers/api/fishmongerController"; import { fishmongerController } from "@/src/controllers/api/fishmongerController";
import { focusController } from "@/src/controllers/api/focusController"; import { focusController } from "@/src/controllers/api/focusController";
import { fusionTreasuresController } from "@/src/controllers/api/fusionTreasuresController"; import { fusionTreasuresController } from "@/src/controllers/api/fusionTreasuresController";
import { gardeningController } from "@/src/controllers/api/gardeningController";
import { genericUpdateController } from "@/src/controllers/api/genericUpdateController"; import { genericUpdateController } from "@/src/controllers/api/genericUpdateController";
import { getAllianceController } from "@/src/controllers/api/getAllianceController"; import { getAllianceController } from "@/src/controllers/api/getAllianceController";
import { getDailyDealStockLevelsController } from "@/src/controllers/api/getDailyDealStockLevelsController"; import { getDailyDealStockLevelsController } from "@/src/controllers/api/getDailyDealStockLevelsController";
@ -240,6 +241,7 @@ apiRouter.post("/findSessions.php", findSessionsController);
apiRouter.post("/fishmonger.php", fishmongerController); apiRouter.post("/fishmonger.php", fishmongerController);
apiRouter.post("/focus.php", focusController); apiRouter.post("/focus.php", focusController);
apiRouter.post("/fusionTreasures.php", fusionTreasuresController); apiRouter.post("/fusionTreasures.php", fusionTreasuresController);
apiRouter.post("/gardening.php", gardeningController);
apiRouter.post("/genericUpdate.php", genericUpdateController); apiRouter.post("/genericUpdate.php", genericUpdateController);
apiRouter.post("/getAlliance.php", getAllianceController); apiRouter.post("/getAlliance.php", getAllianceController);
apiRouter.post("/getFriends.php", getFriendsController); apiRouter.post("/getFriends.php", getFriendsController);

View File

@ -10,6 +10,7 @@ import { getAccountInfoController } from "@/src/controllers/custom/getAccountInf
import { renameAccountController } from "@/src/controllers/custom/renameAccountController"; import { renameAccountController } from "@/src/controllers/custom/renameAccountController";
import { ircDroppedController } from "@/src/controllers/custom/ircDroppedController"; import { ircDroppedController } from "@/src/controllers/custom/ircDroppedController";
import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController"; import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController";
import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController";
import { createAccountController } from "@/src/controllers/custom/createAccountController"; import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { createMessageController } from "@/src/controllers/custom/createMessageController"; import { createMessageController } from "@/src/controllers/custom/createMessageController";
@ -35,6 +36,7 @@ customRouter.get("/getAccountInfo", getAccountInfoController);
customRouter.get("/renameAccount", renameAccountController); customRouter.get("/renameAccount", renameAccountController);
customRouter.get("/ircDropped", ircDroppedController); customRouter.get("/ircDropped", ircDroppedController);
customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController); customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
customRouter.post("/createAccount", createAccountController); customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController); customRouter.post("/createMessage", createMessageController);

View File

@ -24,6 +24,7 @@ interface IConfig {
infiniteEndo?: boolean; infiniteEndo?: boolean;
infiniteRegalAya?: boolean; infiniteRegalAya?: boolean;
infiniteHelminthMaterials?: boolean; infiniteHelminthMaterials?: boolean;
dontSubtractConsumables?: boolean;
unlockAllShipFeatures?: boolean; unlockAllShipFeatures?: boolean;
unlockAllShipDecorations?: boolean; unlockAllShipDecorations?: boolean;
unlockAllFlavourItems?: boolean; unlockAllFlavourItems?: boolean;

View File

@ -154,23 +154,22 @@ export const addStartingGear = async (
//TODO: properly merge weapon bin changes it is currently static here //TODO: properly merge weapon bin changes it is currently static here
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, undefined, inventoryChanges); addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, { IsNew: false }, inventoryChanges);
addEquipment(inventory, "Pistols", Pistols[0].ItemType, undefined, inventoryChanges); addEquipment(inventory, "Pistols", Pistols[0].ItemType, { IsNew: false }, inventoryChanges);
addEquipment(inventory, "Melee", Melee[0].ItemType, undefined, inventoryChanges); addEquipment(inventory, "Melee", Melee[0].ItemType, { IsNew: false }, inventoryChanges);
await addPowerSuit(inventory, Suits[0].ItemType, inventoryChanges); await addPowerSuit(inventory, Suits[0].ItemType, { IsNew: false }, inventoryChanges);
addEquipment( addEquipment(
inventory, inventory,
"DataKnives", "DataKnives",
"/Lotus/Weapons/Tenno/HackingDevices/TnHackingDevice/TnHackingDeviceWeapon", "/Lotus/Weapons/Tenno/HackingDevices/TnHackingDevice/TnHackingDeviceWeapon",
undefined, { XP: 450_000, IsNew: false },
inventoryChanges, inventoryChanges
{ XP: 450_000 }
); );
addEquipment( addEquipment(
inventory, inventory,
"Scoops", "Scoops",
"/Lotus/Weapons/Tenno/Speedball/SpeedballWeaponTest", "/Lotus/Weapons/Tenno/Speedball/SpeedballWeaponTest",
undefined, { IsNew: false },
inventoryChanges inventoryChanges
); );
@ -213,6 +212,15 @@ export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, del
for (const key in delta) { for (const key in delta) {
if (!(key in InventoryChanges)) { if (!(key in InventoryChanges)) {
InventoryChanges[key] = delta[key]; InventoryChanges[key] = delta[key];
} else if (key == "MiscItems") {
for (const deltaItem of delta[key]!) {
const existing = InventoryChanges[key]!.find(x => x.ItemType == deltaItem.ItemType);
if (existing) {
existing.ItemCount += deltaItem.ItemCount;
} else {
InventoryChanges[key]!.push(deltaItem);
}
}
} else if (Array.isArray(delta[key])) { } else if (Array.isArray(delta[key])) {
const left = InventoryChanges[key] as object[]; const left = InventoryChanges[key] as object[];
const right: object[] = delta[key]; const right: object[] = delta[key];
@ -522,14 +530,7 @@ export const addItem = async (
] ]
}); });
} }
const inventoryChanges = addEquipment( const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName, defaultOverwrites);
inventory,
weapon.productCategory,
typeName,
[],
{},
defaultOverwrites
);
if (weapon.additionalItems) { if (weapon.additionalItems) {
for (const item of weapon.additionalItems) { for (const item of weapon.additionalItems) {
combineInventoryChanges(inventoryChanges, await addItem(inventory, item, 1)); combineInventoryChanges(inventoryChanges, await addItem(inventory, item, 1));
@ -630,12 +631,9 @@ export const addItem = async (
switch (typeName.substr(1).split("/")[2]) { switch (typeName.substr(1).split("/")[2]) {
default: { default: {
return { return {
...(await addPowerSuit( ...(await addPowerSuit(inventory, typeName, {
inventory, Features: premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined
typeName, })),
{},
premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined
)),
...occupySlot(inventory, InventorySlot.SUITS, premiumPurchase) ...occupySlot(inventory, InventorySlot.SUITS, premiumPurchase)
}; };
} }
@ -855,8 +853,8 @@ const addSentinelWeapon = (
export const addPowerSuit = async ( export const addPowerSuit = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
powersuitName: string, powersuitName: string,
inventoryChanges: IInventoryChanges = {}, defaultOverwrites?: Partial<IEquipmentDatabase>,
features?: number inventoryChanges: IInventoryChanges = {}
): Promise<IInventoryChanges> => { ): Promise<IInventoryChanges> => {
const powersuit = ExportWarframes[powersuitName] as IPowersuit | undefined; const powersuit = ExportWarframes[powersuitName] as IPowersuit | undefined;
const exalted = powersuit?.exalted ?? []; const exalted = powersuit?.exalted ?? [];
@ -870,15 +868,20 @@ export const addPowerSuit = async (
} }
} }
} }
const suitIndex = const suit: Omit<IEquipmentDatabase, "_id"> = Object.assign(
inventory.Suits.push({ {
ItemType: powersuitName, ItemType: powersuitName,
Configs: [], Configs: [],
UpgradeVer: 101, UpgradeVer: 101,
XP: 0, XP: 0,
Features: features,
IsNew: true IsNew: true
}) - 1; },
defaultOverwrites
);
if (!suit.IsNew) {
suit.IsNew = undefined;
}
const suitIndex = inventory.Suits.push(suit) - 1;
inventoryChanges.Suits ??= []; inventoryChanges.Suits ??= [];
inventoryChanges.Suits.push(inventory.Suits[suitIndex].toJSON<IEquipmentClient>()); inventoryChanges.Suits.push(inventory.Suits[suitIndex].toJSON<IEquipmentClient>());
return inventoryChanges; return inventoryChanges;
@ -1199,20 +1202,21 @@ export const addEquipment = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
category: TEquipmentKey, category: TEquipmentKey,
type: string, type: string,
modularParts?: string[], defaultOverwrites?: Partial<IEquipmentDatabase>,
inventoryChanges: IInventoryChanges = {}, inventoryChanges: IInventoryChanges = {}
defaultOverwrites?: Partial<IEquipmentDatabase>
): IInventoryChanges => { ): IInventoryChanges => {
const equipment = Object.assign( const equipment: Omit<IEquipmentDatabase, "_id"> = Object.assign(
{ {
ItemType: type, ItemType: type,
Configs: [], Configs: [],
XP: 0, XP: 0,
ModularParts: modularParts, IsNew: category != "CrewShipWeapons" && category != "CrewShipSalvagedWeapons"
IsNew: category != "CrewShipWeapons" && category != "CrewShipSalvagedWeapons" ? true : undefined
}, },
defaultOverwrites defaultOverwrites
); );
if (!equipment.IsNew) {
equipment.IsNew = undefined;
}
const index = inventory[category].push(equipment) - 1; const index = inventory[category].push(equipment) - 1;
inventoryChanges[category] ??= []; inventoryChanges[category] ??= [];
@ -1468,6 +1472,22 @@ export const addGearExpByCategory = (
}); });
}; };
export const addMiscItem = (
inventory: TInventoryDatabaseDocument,
type: string,
count: number,
inventoryChanges: IInventoryChanges
): void => {
const miscItemChanges: IMiscItem[] = [
{
ItemType: type,
ItemCount: count
}
];
addMiscItems(inventory, miscItemChanges);
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
};
export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray: IMiscItem[]): void => { export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray: IMiscItem[]): void => {
const { MiscItems } = inventory; const { MiscItems } = inventory;

View File

@ -30,6 +30,7 @@ import {
addMods, addMods,
addRecipes, addRecipes,
addShipDecorations, addShipDecorations,
addSkin,
addStanding, addStanding,
combineInventoryChanges, combineInventoryChanges,
generateRewardSeed, generateRewardSeed,
@ -69,16 +70,22 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
return rotations; return rotations;
} }
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const missionIndex: number | undefined = ExportRegions[rewardInfo.node]?.missionIndex;
// For Rescue missions // For Rescue missions
if (rewardInfo.node in ExportRegions && ExportRegions[rewardInfo.node].missionIndex == 3 && rewardInfo.rewardTier) { if (missionIndex == 3 && rewardInfo.rewardTier) {
return [rewardInfo.rewardTier]; return [rewardInfo.rewardTier];
} }
const rotationCount = rewardInfo.rewardQualifications?.length || 0; const rotationCount = rewardInfo.rewardQualifications?.length || 0;
// Empty or absent rewardQualifications should not give rewards: // Empty or absent rewardQualifications should not give rewards when:
// - Aborting a railjack mission (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741)
// - Completing only 1 zone of (E)SO (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1823) // - Completing only 1 zone of (E)SO (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1823)
// - Aborting a railjack mission (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741)
if (rotationCount == 0 && missionIndex != 30 && missionIndex != 32) {
return [0];
}
const rotationPattern = const rotationPattern =
tierOverride === undefined tierOverride === undefined
@ -184,6 +191,12 @@ export const addMissionInventoryUpdates = async (
if (inventoryUpdates.RewardInfo.NemesisAbandonedRewards) { if (inventoryUpdates.RewardInfo.NemesisAbandonedRewards) {
inventory.NemesisAbandonedRewards = inventoryUpdates.RewardInfo.NemesisAbandonedRewards; inventory.NemesisAbandonedRewards = inventoryUpdates.RewardInfo.NemesisAbandonedRewards;
} }
if (inventoryUpdates.RewardInfo.NemesisHenchmenKills && inventory.Nemesis) {
inventory.Nemesis.HenchmenKilled += inventoryUpdates.RewardInfo.NemesisHenchmenKills;
}
if (inventoryUpdates.RewardInfo.NemesisHintProgress && inventory.Nemesis) {
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress;
}
if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) { if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) {
// e.g. for Profit-Taker Phase 1: // e.g. for Profit-Taker Phase 1:
// JobTier: -6, // JobTier: -6,
@ -265,7 +278,14 @@ export const addMissionInventoryUpdates = async (
addMiscItems(inventory, value); addMiscItems(inventory, value);
break; break;
case "Consumables": case "Consumables":
if (config.dontSubtractConsumables) {
addConsumables(
inventory,
value.filter(x => x.ItemCount > 0)
);
} else {
addConsumables(inventory, value); addConsumables(inventory, value);
}
break; break;
case "Recipes": case "Recipes":
addRecipes(inventory, value); addRecipes(inventory, value);
@ -411,6 +431,11 @@ export const addMissionInventoryUpdates = async (
upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress
}); });
break; break;
case "WeaponSkins":
for (const item of value) {
addSkin(inventory, item.ItemType);
}
break;
case "Boosters": case "Boosters":
value.forEach(booster => { value.forEach(booster => {
addBooster(booster.ItemType, booster.ExpiryDate, inventory); addBooster(booster.ItemType, booster.ExpiryDate, inventory);
@ -1277,6 +1302,9 @@ function getRandomMissionDrops(
// Invasion assassination has Phorid has the boss who should drop Nyx parts // Invasion assassination has Phorid has the boss who should drop Nyx parts
// TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic // TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic
rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"]; rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"];
} else if (RewardInfo.sortieId && region.missionIndex != 0) {
// Sortie mission types differ from the underlying node and hence also don't give rewards from the underlying nodes. Assassinations are an exception to this.
rewardManifests = [];
} else { } else {
rewardManifests = region.rewardManifests; rewardManifests = region.rewardManifests;
} }

View File

@ -1,9 +1,14 @@
import { PersonalRooms } from "@/src/models/personalRoomsModel"; import { PersonalRooms } from "@/src/models/personalRoomsModel";
import { addItem, getInventory } from "@/src/services/inventoryService"; import { addItem, getInventory } from "@/src/services/inventoryService";
import { TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes"; import { TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes";
import { IGardeningDatabase } from "../types/shipTypes";
import { getRandomElement } from "./rngService";
export const getPersonalRooms = async (accountId: string): Promise<TPersonalRoomsDatabaseDocument> => { export const getPersonalRooms = async (
const personalRooms = await PersonalRooms.findOne({ personalRoomsOwnerId: accountId }); accountId: string,
projection?: string
): Promise<TPersonalRoomsDatabaseDocument> => {
const personalRooms = await PersonalRooms.findOne({ personalRoomsOwnerId: accountId }, projection);
if (!personalRooms) { if (!personalRooms) {
throw new Error(`personal rooms not found for account ${accountId}`); throw new Error(`personal rooms not found for account ${accountId}`);
@ -25,3 +30,64 @@ export const updateShipFeature = async (accountId: string, shipFeature: string):
await addItem(inventory, shipFeature, -1); await addItem(inventory, shipFeature, -1);
await inventory.save(); await inventory.save();
}; };
export const createGarden = (): IGardeningDatabase => {
const plantTypes = [
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantA",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantB",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantC",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantD",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantE",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantF"
];
const endTime = new Date((Math.trunc(Date.now() / 1000) + 79200) * 1000); // Plants will take 22 hours to grow
return {
Planters: [
{
Name: "Garden0",
Plants: [
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 0
},
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 1
}
]
},
{
Name: "Garden1",
Plants: [
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 0
},
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 1
}
]
},
{
Name: "Garden2",
Plants: [
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 0
},
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 1
}
]
}
]
};
};

View File

@ -66,6 +66,18 @@ export const handlePurchase = async (
if (!offer) { if (!offer) {
throw new Error(`unknown vendor offer: ${ItemId ? ItemId : purchaseRequest.PurchaseParams.StoreItem}`); throw new Error(`unknown vendor offer: ${ItemId ? ItemId : purchaseRequest.PurchaseParams.StoreItem}`);
} }
if (offer.RegularPrice) {
combineInventoryChanges(
prePurchaseInventoryChanges,
updateCurrency(inventory, offer.RegularPrice[0], false)
);
}
if (offer.PremiumPrice) {
combineInventoryChanges(
prePurchaseInventoryChanges,
updateCurrency(inventory, offer.PremiumPrice[0], true)
);
}
if (offer.ItemPrices) { if (offer.ItemPrices) {
handleItemPrices( handleItemPrices(
inventory, inventory,
@ -170,6 +182,9 @@ export const handlePurchase = async (
purchaseResponse.InventoryChanges, purchaseResponse.InventoryChanges,
updateCurrency(inventory, offer.RegularPrice, false) updateCurrency(inventory, offer.RegularPrice, false)
); );
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
throw new Error(`vendor purchase should not have an expected price`);
}
const invItem: IMiscItem = { const invItem: IMiscItem = {
ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks", ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
@ -229,6 +244,12 @@ export const handlePurchase = async (
updateCurrency(inventory, offer.credits, false) updateCurrency(inventory, offer.credits, false)
); );
} }
if (typeof offer.platinum == "number") {
combineInventoryChanges(
purchaseResponse.InventoryChanges,
updateCurrency(inventory, offer.platinum, true)
);
}
if (offer.itemPrices) { if (offer.itemPrices) {
handleItemPrices( handleItemPrices(
inventory, inventory,
@ -239,6 +260,9 @@ export const handlePurchase = async (
} }
} }
} }
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
throw new Error(`vendor purchase should not have an expected price`);
}
break; break;
case 18: { case 18: {
if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) { if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) {

View File

@ -161,6 +161,11 @@ export const handleInventoryItemConfigChange = async (
} }
break; break;
} }
case "LotusCustomization": {
logger.debug(`saved LotusCustomization`, equipmentChanges.LotusCustomization);
inventory.LotusCustomization = equipmentChanges.LotusCustomization;
break;
}
default: { default: {
if (equipmentKeys.includes(equipmentName as TEquipmentKey) && equipmentName != "ValidNewLoadoutId") { if (equipmentKeys.includes(equipmentName as TEquipmentKey) && equipmentName != "ValidNewLoadoutId") {
logger.debug(`general Item config saved of type ${equipmentName}`, { logger.debug(`general Item config saved of type ${equipmentName}`, {

View File

@ -50,21 +50,27 @@ const sortieBossToFaction: Record<string, string> = {
SORTIE_BOSS_PHORID: "FC_INFESTATION", SORTIE_BOSS_PHORID: "FC_INFESTATION",
SORTIE_BOSS_LEPHANTIS: "FC_INFESTATION", SORTIE_BOSS_LEPHANTIS: "FC_INFESTATION",
SORTIE_BOSS_INFALAD: "FC_INFESTATION", SORTIE_BOSS_INFALAD: "FC_INFESTATION",
SORTIE_BOSS_CORRUPTED_VOR: "FC_CORRUPTED" SORTIE_BOSS_CORRUPTED_VOR: "FC_OROKIN"
}; };
const sortieFactionToSystemIndexes: Record<string, number[]> = { const sortieFactionToSystemIndexes: Record<string, number[]> = {
FC_GRINEER: [0, 2, 3, 5, 6, 9, 11, 18], FC_GRINEER: [0, 2, 3, 5, 6, 9, 11, 18],
FC_CORPUS: [1, 4, 7, 8, 12, 15], FC_CORPUS: [1, 4, 7, 8, 12, 15],
FC_INFESTATION: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15], FC_INFESTATION: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15],
FC_CORRUPTED: [14] FC_OROKIN: [14]
}; };
const sortieFactionToFactionIndexes: Record<string, number[]> = { const sortieFactionToFactionIndexes: Record<string, number[]> = {
FC_GRINEER: [0], FC_GRINEER: [0],
FC_CORPUS: [1], FC_CORPUS: [1],
FC_INFESTATION: [0, 1, 2], FC_INFESTATION: [0, 1, 2],
FC_CORRUPTED: [3] FC_OROKIN: [3]
};
const sortieFactionToSpecialMissionTileset: Record<string, string> = {
FC_GRINEER: "GrineerGalleonTileset",
FC_CORPUS: "CorpusShipTileset",
FC_INFESTATION: "CorpusShipTileset"
}; };
const sortieBossNode: Record<string, string> = { const sortieBossNode: Record<string, string> = {
@ -273,14 +279,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
if ( if (
sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) && sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) && sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
!isArchwingMission(value) && key in sortieTilesets
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
value.missionIndex != 10 && // Exclude MT_PVP (for relays)
value.missionIndex != 21 && // Exclude MT_PURIFY
value.missionIndex != 22 && // Exclude MT_ARENA
value.missionIndex != 23 && // Exclude MT_JUNCTION
(value.missionIndex != 28 || value.systemIndex == 2) && // MT_LANDSCAPE only on Earth
value.missionIndex < 29
) { ) {
if (!availableMissionIndexes.includes(value.missionIndex)) { if (!availableMissionIndexes.includes(value.missionIndex)) {
availableMissionIndexes.push(value.missionIndex); availableMissionIndexes.push(value.missionIndex);
@ -289,21 +288,36 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
} }
} }
const specialMissionTypes = [1, 3, 5, 9];
if (!(sortieBossToFaction[boss] in sortieFactionToSpecialMissionTileset)) {
for (const missionType of specialMissionTypes) {
const i = availableMissionIndexes.indexOf(missionType);
if (i != -1) {
availableMissionIndexes.splice(i, 1);
}
}
}
const selectedNodes: ISortieMission[] = []; const selectedNodes: ISortieMission[] = [];
const missionTypes = new Set(); const missionTypes = new Set();
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
const randomIndex = rng.randomInt(0, nodes.length - 1); let randomIndex;
const node = nodes[randomIndex]; let node;
let missionIndex = ExportRegions[node].missionIndex; let missionIndex;
do {
randomIndex = rng.randomInt(0, nodes.length - 1);
node = nodes[randomIndex];
if ( missionIndex = ExportRegions[node].missionIndex;
!["SolNode404", "SolNode411"].includes(node) && // for some reason the game doesn't like missionType changes for these missions if (missionIndex != 28) {
missionIndex != 28 &&
rng.randomInt(0, 2) == 2
) {
missionIndex = rng.randomElement(availableMissionIndexes); missionIndex = rng.randomElement(availableMissionIndexes);
} }
} while (
specialMissionTypes.indexOf(missionIndex) != -1 &&
sortieTilesets[node as keyof typeof sortieTilesets] !=
sortieFactionToSpecialMissionTileset[sortieBossToFaction[boss]]
);
if (i == 2 && rng.randomInt(0, 2) == 2) { if (i == 2 && rng.randomInt(0, 2) == 2) {
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY"); const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");

View File

@ -328,7 +328,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
BlessingCooldown?: IMongoDate; BlessingCooldown?: IMongoDate;
CrewShipRawSalvage: ITypeCount[]; CrewShipRawSalvage: ITypeCount[];
CrewMembers: ICrewMemberClient[]; CrewMembers: ICrewMemberClient[];
LotusCustomization: ILotusCustomization; LotusCustomization?: ILotusCustomization;
UseAdultOperatorLoadout?: boolean; UseAdultOperatorLoadout?: boolean;
NemesisAbandonedRewards: string[]; NemesisAbandonedRewards: string[];
LastInventorySync: IOid; LastInventorySync: IOid;

View File

@ -8,6 +8,8 @@ export interface IMissionReward {
TypeName?: string; TypeName?: string;
UpgradeLevel?: number; UpgradeLevel?: number;
ItemCount: number; ItemCount: number;
DailyCooldown?: boolean;
Rarity?: number;
TweetText?: string; TweetText?: string;
ProductCategory?: string; ProductCategory?: string;
FromEnemyCache?: boolean; FromEnemyCache?: boolean;

View File

@ -1,12 +1,12 @@
import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { import {
IApartment,
IRoom, IRoom,
IPlacedDecosDatabase, IPlacedDecosDatabase,
ITailorShop, ITailorShop,
ITailorShopDatabase, ITailorShopDatabase,
TBootLocation, TBootLocation,
IApartmentDatabase IApartmentDatabase,
IApartmentClient
} from "@/src/types/shipTypes"; } from "@/src/types/shipTypes";
import { Document, Model, Types } from "mongoose"; import { Document, Model, Types } from "mongoose";
@ -21,10 +21,10 @@ export interface IOrbiter {
BootLocation?: TBootLocation; BootLocation?: TBootLocation;
} }
export interface IPersonalRooms { export interface IPersonalRoomsClient {
ShipInteriorColors: IColor; ShipInteriorColors: IColor;
Ship: IOrbiter; Ship: IOrbiter;
Apartment: IApartment; Apartment: IApartmentClient;
TailorShop: ITailorShop; TailorShop: ITailorShop;
} }

View File

@ -20,7 +20,8 @@ import {
IDiscoveredMarker, IDiscoveredMarker,
ILockedWeaponGroupClient, ILockedWeaponGroupClient,
ILoadOutPresets, ILoadOutPresets,
IInvasionProgressClient IInvasionProgressClient,
IWeaponSkinClient
} from "./inventoryTypes/inventoryTypes"; } from "./inventoryTypes/inventoryTypes";
import { IGroup } from "./loginTypes"; import { IGroup } from "./loginTypes";
@ -101,6 +102,7 @@ export type IMissionInventoryUpdateRequest = {
}[]; }[];
CollectibleScans?: ICollectibleEntry[]; CollectibleScans?: ICollectibleEntry[];
Upgrades?: IUpgradeClient[]; // riven challenge progress Upgrades?: IUpgradeClient[]; // riven challenge progress
WeaponSkins?: IWeaponSkinClient[];
StrippedItems?: { StrippedItems?: {
DropTable: string; DropTable: string;
DROP_MOD?: number[]; DROP_MOD?: number[];
@ -156,6 +158,8 @@ export interface IRewardInfo {
lostTargetWave?: number; lostTargetWave?: number;
defenseTargetCount?: number; defenseTargetCount?: number;
NemesisAbandonedRewards?: string[]; NemesisAbandonedRewards?: string[];
NemesisHenchmenKills?: number;
NemesisHintProgress?: number;
EOM_AFK?: number; EOM_AFK?: number;
rewardQualifications?: string; // did a Survival for 5 minutes and this was "1" rewardQualifications?: string; // did a Survival for 5 minutes and this was "1"
PurgatoryRewardQualifications?: string; PurgatoryRewardQualifications?: string;

View File

@ -6,7 +6,8 @@ import {
ICrewShipMembersClient, ICrewShipMembersClient,
ICrewShipWeapon, ICrewShipWeapon,
IFlavourItem, IFlavourItem,
ILoadoutConfigClient ILoadoutConfigClient,
ILotusCustomization
} from "./inventoryTypes/inventoryTypes"; } from "./inventoryTypes/inventoryTypes";
export interface ISaveLoadoutRequest { export interface ISaveLoadoutRequest {
@ -43,6 +44,7 @@ export interface ISaveLoadoutRequest {
EquippedEmotes: string[]; EquippedEmotes: string[];
UseAdultOperatorLoadout: boolean; UseAdultOperatorLoadout: boolean;
WeaponSkins: IItemEntry; WeaponSkins: IItemEntry;
LotusCustomization: ILotusCustomization;
} }
export type ISaveLoadoutRequestNoUpgradeVer = Omit<ISaveLoadoutRequest, "UpgradeVer">; export type ISaveLoadoutRequestNoUpgradeVer = Omit<ISaveLoadoutRequest, "UpgradeVer">;

View File

@ -1,12 +1,12 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IOid } from "@/src/types/commonTypes"; import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { ILoadoutClient } from "./saveLoadoutTypes"; import { ILoadoutClient } from "./saveLoadoutTypes";
export interface IGetShipResponse { export interface IGetShipResponse {
ShipOwnerId: string; ShipOwnerId: string;
Ship: IShip; Ship: IShip;
Apartment: IApartment; Apartment: IApartmentClient;
TailorShop: ITailorShop; TailorShop: ITailorShop;
LoadOutInventory: { LoadOutPresets: ILoadoutClient }; LoadOutInventory: { LoadOutPresets: ILoadoutClient };
} }
@ -51,28 +51,42 @@ export interface IRoom {
PlacedDecos?: IPlacedDecosDatabase[]; PlacedDecos?: IPlacedDecosDatabase[];
} }
export interface IPlants { export interface IPlantClient {
PlantType: string; PlantType: string;
EndTime: IOid; EndTime: IMongoDate;
PlotIndex: number; PlotIndex: number;
} }
export interface IPlanters {
export interface IPlantDatabase extends Omit<IPlantClient, "EndTime"> {
EndTime: Date;
}
export interface IPlanterClient {
Name: string; Name: string;
Plants: IPlants[]; Plants: IPlantClient[];
} }
export interface IGardening { export interface IPlanterDatabase {
Planters?: IPlanters[]; Name: string;
Plants: IPlantDatabase[];
} }
export interface IApartment { export interface IGardeningClient {
Gardening: IGardening; Planters: IPlanterClient[];
}
export interface IGardeningDatabase {
Planters: IPlanterDatabase[];
}
export interface IApartmentClient {
Gardening: IGardeningClient;
Rooms: IRoom[]; Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadout[]; FavouriteLoadouts: IFavouriteLoadout[];
} }
export interface IApartmentDatabase { export interface IApartmentDatabase {
Gardening: IGardening; Gardening: IGardeningDatabase;
Rooms: IRoom[]; Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadoutDatabase[]; FavouriteLoadouts: IFavouriteLoadoutDatabase[];
} }

View File

@ -10,6 +10,7 @@ export interface IItemManifest {
StoreItem: string; StoreItem: string;
ItemPrices?: IItemPrice[]; ItemPrices?: IItemPrice[];
RegularPrice?: number[]; RegularPrice?: number[];
PremiumPrice?: number[];
Bin: string; Bin: string;
QuantityMultiplier: number; QuantityMultiplier: number;
Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing. Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.

View File

@ -510,8 +510,9 @@
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body d-flex flex-wrap gap-2"> <div class="card-body d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_bulkAddMods"></button> <button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_addMissingUnrankedMods"></button>
<button class="btn btn-danger" onclick="doRemoveUnrankedMods();" data-loc="mods_removeUnranked"></button> <button class="btn btn-danger" onclick="doRemoveUnrankedMods();" data-loc="mods_removeUnranked"></button>
<button class="btn btn-primary" onclick="doAddMissingMaxRankMods();" data-loc="mods_addMissingMaxRankMods"></button>
</div> </div>
</div> </div>
</div> </div>
@ -593,6 +594,10 @@
<input class="form-check-input" type="checkbox" id="infiniteHelminthMaterials" /> <input class="form-check-input" type="checkbox" id="infiniteHelminthMaterials" />
<label class="form-check-label" for="infiniteHelminthMaterials" data-loc="cheats_infiniteHelminthMaterials"></label> <label class="form-check-label" for="infiniteHelminthMaterials" data-loc="cheats_infiniteHelminthMaterials"></label>
</div> </div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="dontSubtractConsumables" />
<label class="form-check-label" for="dontSubtractConsumables" data-loc="cheats_dontSubtractConsumables"></label>
</div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllShipFeatures" /> <input class="form-check-input" type="checkbox" id="unlockAllShipFeatures" />
<label class="form-check-label" for="unlockAllShipFeatures" data-loc="cheats_unlockAllShipFeatures"></label> <label class="form-check-label" for="unlockAllShipFeatures" data-loc="cheats_unlockAllShipFeatures"></label>

View File

@ -1795,6 +1795,14 @@ function doRemoveUnrankedMods() {
}); });
} }
function doAddMissingMaxRankMods() {
revalidateAuthz(() => {
fetch("/custom/addMissingMaxRankMods?" + window.authz).then(() => {
updateInventory();
});
});
}
// Powersuit Route // Powersuit Route
single.getRoute("#powersuit-route").on("beforeload", function () { single.getRoute("#powersuit-route").on("beforeload", function () {

View File

@ -120,8 +120,9 @@ dict = {
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`, mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
mods_rivens: `Rivens`, mods_rivens: `Rivens`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `Fehlende Mods hinzufügen`, mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_removeUnranked: `Mods ohne Rang entfernen`, mods_removeUnranked: `Mods ohne Rang entfernen`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`, cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`,
cheats_server: `Server`, cheats_server: `Server`,
cheats_skipTutorial: `Tutorial überspringen`, cheats_skipTutorial: `Tutorial überspringen`,
@ -133,6 +134,7 @@ dict = {
cheats_infiniteEndo: `Unendlich Endo`, cheats_infiniteEndo: `Unendlich Endo`,
cheats_infiniteRegalAya: `Unendlich Reines Aya`, cheats_infiniteRegalAya: `Unendlich Reines Aya`,
cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`, cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`, cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`, cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`,
cheats_unlockAllFlavourItems: `Alle <abbr title=\"Animationssets, Glyphen, Farbpaletten usw.\">Sammlerstücke</abbr> freischalten`, cheats_unlockAllFlavourItems: `Alle <abbr title=\"Animationssets, Glyphen, Farbpaletten usw.\">Sammlerstücke</abbr> freischalten`,

View File

@ -119,8 +119,9 @@ dict = {
mods_fingerprintHelp: `Need help with the fingerprint?`, mods_fingerprintHelp: `Need help with the fingerprint?`,
mods_rivens: `Rivens`, mods_rivens: `Rivens`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `Add Missing Mods`, mods_addMissingUnrankedMods: `Add Missing Unranked Mods`,
mods_removeUnranked: `Remove Unranked Mods`, mods_removeUnranked: `Remove Unranked Mods`,
mods_addMissingMaxRankMods: `Add Missing Max Rank Mods`,
cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add <code>|DISPLAYNAME|</code> to <code>administratorNames</code> in the config.json.`, cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add <code>|DISPLAYNAME|</code> to <code>administratorNames</code> in the config.json.`,
cheats_server: `Server`, cheats_server: `Server`,
cheats_skipTutorial: `Skip Tutorial`, cheats_skipTutorial: `Skip Tutorial`,
@ -132,6 +133,7 @@ dict = {
cheats_infiniteEndo: `Infinite Endo`, cheats_infiniteEndo: `Infinite Endo`,
cheats_infiniteRegalAya: `Infinite Regal Aya`, cheats_infiniteRegalAya: `Infinite Regal Aya`,
cheats_infiniteHelminthMaterials: `Infinite Helminth Materials`, cheats_infiniteHelminthMaterials: `Infinite Helminth Materials`,
cheats_dontSubtractConsumables: `Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Unlock All Ship Features`, cheats_unlockAllShipFeatures: `Unlock All Ship Features`,
cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`, cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`,
cheats_unlockAllFlavourItems: `Unlock All <abbr title=\"Animation Sets, Glyphs, Palettes, etc.\">Flavor Items</abbr>`, cheats_unlockAllFlavourItems: `Unlock All <abbr title=\"Animation Sets, Glyphs, Palettes, etc.\">Flavor Items</abbr>`,

View File

@ -34,8 +34,8 @@ dict = {
code_rerollsNumber: `Cantidad de reintentos`, code_rerollsNumber: `Cantidad de reintentos`,
code_viewStats: `Ver estadísticas`, code_viewStats: `Ver estadísticas`,
code_rank: `Rango`, code_rank: `Rango`,
code_rankUp: `[UNTRANSLATED] Rank up`, code_rankUp: `Subir de rango`,
code_rankDown: `[UNTRANSLATED] Rank down`, code_rankDown: `Bajar de rango`,
code_count: `Cantidad`, code_count: `Cantidad`,
code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`, code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`,
code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`, code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`,
@ -86,21 +86,21 @@ dict = {
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moa`,
inventory_kubrowPets: `Bestias`, inventory_kubrowPets: `Bestias`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`, inventory_evolutionProgress: `Progreso de evolución Incarnon`,
inventory_bulkAddSuits: `Agregar Warframes faltantes`, inventory_bulkAddSuits: `Agregar Warframes faltantes`,
inventory_bulkAddWeapons: `Agregar armas faltantes`, inventory_bulkAddWeapons: `Agregar armas faltantes`,
inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`, inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`, inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`,
inventory_bulkAddSentinels: `Agregar centinelas faltantes`, inventory_bulkAddSentinels: `Agregar centinelas faltantes`,
inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`, inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`, inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`,
inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`, inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`, inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`, inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
inventory_bulkRankUpSpaceWeapons: `Maximizar rango de todas las armas Archwing`, inventory_bulkRankUpSpaceWeapons: `Maximizar rango de todas las armas Archwing`,
inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`, inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`,
inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`, inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`,
quests_list: `Misiones`, quests_list: `Misiones`,
quests_completeAll: `Completar todas las misiones`, quests_completeAll: `Completar todas las misiones`,
@ -120,8 +120,9 @@ dict = {
mods_fingerprintHelp: `¿Necesitas ayuda con la huella digital?`, mods_fingerprintHelp: `¿Necesitas ayuda con la huella digital?`,
mods_rivens: `Agrietados`, mods_rivens: `Agrietados`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `Agregar mods faltantes`, mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_removeUnranked: `Quitar mods sin rango`, mods_removeUnranked: `Quitar mods sin rango`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>|DISPLAYNAME|</code> a <code>administratorNames</code> en el archivo config.json.`, cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>|DISPLAYNAME|</code> a <code>administratorNames</code> en el archivo config.json.`,
cheats_server: `Servidor`, cheats_server: `Servidor`,
cheats_skipTutorial: `Omitir tutorial`, cheats_skipTutorial: `Omitir tutorial`,
@ -133,6 +134,7 @@ dict = {
cheats_infiniteEndo: `Endo infinito`, cheats_infiniteEndo: `Endo infinito`,
cheats_infiniteRegalAya: `Aya Real infinita`, cheats_infiniteRegalAya: `Aya Real infinita`,
cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`, cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`, cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`,
cheats_unlockAllShipDecorations: `Desbloquear todas las decoraciones de nave`, cheats_unlockAllShipDecorations: `Desbloquear todas las decoraciones de nave`,
cheats_unlockAllFlavourItems: `Desbloquear todos los <abbr title="Conjuntos de animaciones, glifos, paletas, etc.">ítems estéticos</abbr>`, cheats_unlockAllFlavourItems: `Desbloquear todos los <abbr title="Conjuntos de animaciones, glifos, paletas, etc.">ítems estéticos</abbr>`,

View File

@ -120,8 +120,9 @@ dict = {
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`, mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
mods_rivens: `Rivens`, mods_rivens: `Rivens`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `Ajouter les mods manquants`, mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`, mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>|DISPLAYNAME|</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`, cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>|DISPLAYNAME|</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`,
cheats_server: `Serveur`, cheats_server: `Serveur`,
cheats_skipTutorial: `Passer le tutoriel`, cheats_skipTutorial: `Passer le tutoriel`,
@ -133,6 +134,7 @@ dict = {
cheats_infiniteEndo: `Endo infini`, cheats_infiniteEndo: `Endo infini`,
cheats_infiniteRegalAya: `Aya Raffiné infini`, cheats_infiniteRegalAya: `Aya Raffiné infini`,
cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`, cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`, cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`, cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`,
cheats_unlockAllFlavourItems: `Débloquer tous les <abbr title=\"Animations, Glyphes, Palettes, etc.\">Flavor Items</abbr>`, cheats_unlockAllFlavourItems: `Débloquer tous les <abbr title=\"Animations, Glyphes, Palettes, etc.\">Flavor Items</abbr>`,

View File

@ -120,8 +120,9 @@ dict = {
mods_fingerprintHelp: `Нужна помощь с отпечатком?`, mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
mods_rivens: `Моды Разлома`, mods_rivens: `Моды Разлома`,
mods_mods: `Моды`, mods_mods: `Моды`,
mods_bulkAddMods: `Добавить отсутствующие моды`, mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`, mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`, cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
cheats_server: `Сервер`, cheats_server: `Сервер`,
cheats_skipTutorial: `Пропустить обучение`, cheats_skipTutorial: `Пропустить обучение`,
@ -133,6 +134,7 @@ dict = {
cheats_infiniteEndo: `Бесконечное эндо`, cheats_infiniteEndo: `Бесконечное эндо`,
cheats_infiniteRegalAya: `Бесконечная Королевская Айя`, cheats_infiniteRegalAya: `Бесконечная Королевская Айя`,
cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`, cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`, cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`, cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`,
cheats_unlockAllFlavourItems: `Разблокировать все <abbr title=\"Наборы анимаций, глифы, палитры и т. д.\">уникальные предметы</abbr>`, cheats_unlockAllFlavourItems: `Разблокировать все <abbr title=\"Наборы анимаций, глифы, палитры и т. д.\">уникальные предметы</abbr>`,

View File

@ -120,8 +120,9 @@ dict = {
mods_fingerprintHelp: `需要印记相关的帮助?`, mods_fingerprintHelp: `需要印记相关的帮助?`,
mods_rivens: `裂罅MOD`, mods_rivens: `裂罅MOD`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `添加缺失MOD`, mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`, mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中。`, cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中。`,
cheats_server: `服务器`, cheats_server: `服务器`,
cheats_skipTutorial: `跳过教程`, cheats_skipTutorial: `跳过教程`,
@ -133,6 +134,7 @@ dict = {
cheats_infiniteEndo: `无限内融核心`, cheats_infiniteEndo: `无限内融核心`,
cheats_infiniteRegalAya: `无限御品阿耶`, cheats_infiniteRegalAya: `无限御品阿耶`,
cheats_infiniteHelminthMaterials: `无限Helminth材料`, cheats_infiniteHelminthMaterials: `无限Helminth材料`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
cheats_unlockAllShipDecorations: `解锁所有飞船装饰`, cheats_unlockAllShipDecorations: `解锁所有飞船装饰`,
cheats_unlockAllFlavourItems: `解锁所有<abbr title=\"动画组合、图标、调色板等\">装饰物品</abbr>`, cheats_unlockAllFlavourItems: `解锁所有<abbr title=\"动画组合、图标、调色板等\">装饰物品</abbr>`,