merge upstream
This commit is contained in:
commit
1ccfb64496
@ -10,6 +10,8 @@ To get an idea of what functionality you can expect to be missing [have a look t
|
|||||||
|
|
||||||
## config.json
|
## config.json
|
||||||
|
|
||||||
|
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), which has most cheats disabled.
|
||||||
|
|
||||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
||||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
||||||
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.
|
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"unlockExilusEverywhere": false,
|
"unlockExilusEverywhere": false,
|
||||||
"unlockArcanesEverywhere": false,
|
"unlockArcanesEverywhere": false,
|
||||||
"noDailyStandingLimits": false,
|
"noDailyStandingLimits": false,
|
||||||
|
"noDailyFocusLimit": false,
|
||||||
"noArgonCrystalDecay": false,
|
"noArgonCrystalDecay": false,
|
||||||
"noMasteryRankUpCooldown": false,
|
"noMasteryRankUpCooldown": false,
|
||||||
"noVendorPurchaseLimits": true,
|
"noVendorPurchaseLimits": true,
|
||||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -18,7 +18,7 @@
|
|||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"typescript": "^5.5",
|
"typescript": "^5.5",
|
||||||
"warframe-public-export-plus": "^0.5.54",
|
"warframe-public-export-plus": "^0.5.55",
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
@ -3789,9 +3789,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/warframe-public-export-plus": {
|
"node_modules/warframe-public-export-plus": {
|
||||||
"version": "0.5.54",
|
"version": "0.5.55",
|
||||||
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.54.tgz",
|
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.55.tgz",
|
||||||
"integrity": "sha512-27r6qLErr3P8UVDiEzhDAs/BjdAS3vI2CQ58jSI+LClDlj6QL+y1jQe8va/npl3Ft2K8PywLkZ8Yso0j9YzvOA=="
|
"integrity": "sha512-Gnd4FCBVuxm2xWGfu8xxxqPIPSnnTqiEWlpP3rsFpVlQs09RNFnW2PEX9rCZt0f3SvHBv5ssDDrFlzgBHS1yrA=="
|
||||||
},
|
},
|
||||||
"node_modules/warframe-riven-info": {
|
"node_modules/warframe-riven-info": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"typescript": "^5.5",
|
"typescript": "^5.5",
|
||||||
"warframe-public-export-plus": "^0.5.54",
|
"warframe-public-export-plus": "^0.5.55",
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { config } from "@/src/services/configService";
|
import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService";
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
@ -31,43 +30,7 @@ export const contributeGuildClassController: RequestHandler = async (req, res) =
|
|||||||
|
|
||||||
guild.CeremonyContributors.push(new Types.ObjectId(accountId));
|
guild.CeremonyContributors.push(new Types.ObjectId(accountId));
|
||||||
|
|
||||||
// Once required contributor count is hit, the class is committed and there's 72 hours to claim endo.
|
await checkClanAscensionHasRequiredContributors(guild);
|
||||||
if (guild.CeremonyContributors.length == payload.RequiredContributors) {
|
|
||||||
guild.Class = guild.CeremonyClass!;
|
|
||||||
guild.CeremonyClass = undefined;
|
|
||||||
guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000));
|
|
||||||
if (!config.fastClanAscension) {
|
|
||||||
// Send message to all active guild members
|
|
||||||
const members = await GuildMember.find({ guildId: payload.GuildId, status: 0 }, "accountId");
|
|
||||||
for (const member of members) {
|
|
||||||
// somewhat unfaithful as on live the "msg" is not a loctag, but since we don't have the string, we'll let the client fill it in with "arg".
|
|
||||||
await createMessage(member.accountId, [
|
|
||||||
{
|
|
||||||
sndr: guild.Name,
|
|
||||||
msg: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgressDetails",
|
|
||||||
arg: [
|
|
||||||
{
|
|
||||||
Key: "RESETDATE",
|
|
||||||
Tag:
|
|
||||||
guild.CeremonyResetDate.getUTCMonth() +
|
|
||||||
"/" +
|
|
||||||
guild.CeremonyResetDate.getUTCDate() +
|
|
||||||
"/" +
|
|
||||||
(guild.CeremonyResetDate.getUTCFullYear() % 100) +
|
|
||||||
" " +
|
|
||||||
guild.CeremonyResetDate.getUTCHours().toString().padStart(2, "0") +
|
|
||||||
":" +
|
|
||||||
guild.CeremonyResetDate.getUTCMinutes().toString().padStart(2, "0")
|
|
||||||
}
|
|
||||||
],
|
|
||||||
sub: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgress",
|
|
||||||
icon: "/Lotus/Interface/Graphics/ClanTileImages/ClanEnterDojo.png",
|
|
||||||
highPriority: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
|
41
src/controllers/api/crewShipIdentifySalvageController.ts
Normal file
41
src/controllers/api/crewShipIdentifySalvageController.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { addCrewShipSalvagedWeaponSkin, addCrewShipRawSalvage, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { IInnateDamageFingerprint } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { ExportCustoms } from "warframe-public-export-plus";
|
||||||
|
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
|
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "CrewShipSalvagedWeaponSkins CrewShipRawSalvage");
|
||||||
|
const payload = getJSONfromString<ICrewShipIdentifySalvageRequest>(String(req.body));
|
||||||
|
|
||||||
|
const buffs: IFingerprintStat[] = [];
|
||||||
|
for (const upgrade of ExportCustoms[payload.ItemType].randomisedUpgrades!) {
|
||||||
|
buffs.push({ Tag: upgrade.tag, Value: Math.trunc(Math.random() * 0x40000000) });
|
||||||
|
}
|
||||||
|
const inventoryChanges: IInventoryChanges = addCrewShipSalvagedWeaponSkin(
|
||||||
|
inventory,
|
||||||
|
payload.ItemType,
|
||||||
|
JSON.stringify({ compat: payload.ItemType, buffs } satisfies IInnateDamageFingerprint)
|
||||||
|
);
|
||||||
|
|
||||||
|
inventoryChanges.CrewShipRawSalvage = [
|
||||||
|
{
|
||||||
|
ItemType: payload.ItemType,
|
||||||
|
ItemCount: -1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addCrewShipRawSalvage(inventory, inventoryChanges.CrewShipRawSalvage);
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICrewShipIdentifySalvageRequest {
|
||||||
|
ItemType: string;
|
||||||
|
}
|
@ -1,14 +1,12 @@
|
|||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { generateRewardSeed } from "@/src/services/inventoryService";
|
import { generateRewardSeed } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
|
||||||
const rewardSeed = generateRewardSeed();
|
const rewardSeed = generateRewardSeed();
|
||||||
logger.debug(`generated new reward seed: ${rewardSeed}`);
|
|
||||||
await Inventory.updateOne(
|
await Inventory.updateOne(
|
||||||
{
|
{
|
||||||
accountOwnerId: accountId
|
accountOwnerId: accountId
|
||||||
|
@ -16,7 +16,7 @@ export const giveQuestKeyRewardController: RequestHandler = async (req, res) =>
|
|||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
|
const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json(inventoryChanges.InventoryChanges);
|
res.json(inventoryChanges);
|
||||||
//TODO: consider whishlist changes
|
//TODO: consider whishlist changes
|
||||||
};
|
};
|
||||||
|
|
20
src/controllers/api/giveShipDecoAndLoreFragmentController.ts
Normal file
20
src/controllers/api/giveShipDecoAndLoreFragmentController.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "LoreFragmentScans ShipDecorations");
|
||||||
|
const data = getJSONfromString<IGiveShipDecoAndLoreFragmentRequest>(String(req.body));
|
||||||
|
addLoreFragmentScans(inventory, data.LoreFragmentScans);
|
||||||
|
addShipDecorations(inventory, data.ShipDecorations);
|
||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IGiveShipDecoAndLoreFragmentRequest {
|
||||||
|
LoreFragmentScans: ILoreFragmentScan[];
|
||||||
|
ShipDecorations: ITypeCount[];
|
||||||
|
}
|
@ -14,20 +14,23 @@ import {
|
|||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import {
|
import {
|
||||||
|
addCrewShipWeaponSkin,
|
||||||
addItem,
|
addItem,
|
||||||
addMiscItems,
|
addMiscItems,
|
||||||
addRecipes,
|
addRecipes,
|
||||||
combineInventoryChanges,
|
combineInventoryChanges,
|
||||||
getInventory,
|
getInventory,
|
||||||
|
occupySlot,
|
||||||
updateCurrency
|
updateCurrency
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
|
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
|
||||||
import { GuildMember } from "@/src/models/guildModel";
|
import { GuildMember } from "@/src/models/guildModel";
|
||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
export const guildTechController: RequestHandler = async (req, res) => {
|
export const guildTechController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -99,6 +102,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
State: 0,
|
State: 0,
|
||||||
ReqCredits: recipe.price,
|
ReqCredits: recipe.price,
|
||||||
ItemType: data.RecipeType,
|
ItemType: data.RecipeType,
|
||||||
|
ProductCategory: data.TechProductCategory,
|
||||||
|
CategoryItemId: data.CategoryItemId,
|
||||||
ReqItems: recipe.ingredients
|
ReqItems: recipe.ingredients
|
||||||
}) - 1
|
}) - 1
|
||||||
];
|
];
|
||||||
@ -222,33 +227,44 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (data.Action.split(",")[0] == "Buy") {
|
} else if (data.Action.split(",")[0] == "Buy") {
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
|
||||||
res.status(400).send("-1").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const purchase = data as IGuildTechBuyRequest;
|
const purchase = data as IGuildTechBuyRequest;
|
||||||
const quantity = parseInt(data.Action.split(",")[1]);
|
if (purchase.Mode == "Guild") {
|
||||||
const recipeChanges = [
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
{
|
if (
|
||||||
ItemType: purchase.RecipeType,
|
!hasAccessToDojo(inventory) ||
|
||||||
ItemCount: quantity
|
!(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))
|
||||||
|
) {
|
||||||
|
res.status(400).send("-1").end();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
];
|
const quantity = parseInt(data.Action.split(",")[1]);
|
||||||
addRecipes(inventory, recipeChanges);
|
const recipeChanges = [
|
||||||
const currencyChanges = updateCurrency(
|
{
|
||||||
inventory,
|
ItemType: purchase.RecipeType,
|
||||||
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
|
ItemCount: quantity
|
||||||
false
|
}
|
||||||
);
|
];
|
||||||
await inventory.save();
|
addRecipes(inventory, recipeChanges);
|
||||||
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
const currencyChanges = updateCurrency(
|
||||||
res.json({
|
inventory,
|
||||||
inventoryChanges: {
|
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
|
||||||
...currencyChanges,
|
false
|
||||||
Recipes: recipeChanges
|
);
|
||||||
}
|
await inventory.save();
|
||||||
});
|
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||||
|
res.json({
|
||||||
|
inventoryChanges: {
|
||||||
|
...currencyChanges,
|
||||||
|
Recipes: recipeChanges
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const inventoryChanges = claimSalvagedComponent(inventory, purchase.CategoryItemId!);
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
inventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (data.Action == "Fabricate") {
|
} else if (data.Action == "Fabricate") {
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||||
@ -289,9 +305,18 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||||
await guild.save();
|
await guild.save();
|
||||||
res.end();
|
res.end();
|
||||||
|
} else if (data.Action == "Rush" && data.CategoryItemId) {
|
||||||
|
const inventoryChanges: IInventoryChanges = {
|
||||||
|
...updateCurrency(inventory, 20, true),
|
||||||
|
...claimSalvagedComponent(inventory, data.CategoryItemId)
|
||||||
|
};
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
inventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
throw new Error(`unknown guildTech action: ${data.Action}`);
|
throw new Error(`unhandled guildTech request`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -301,15 +326,15 @@ type TGuildTechRequest =
|
|||||||
| IGuildTechContributeRequest;
|
| IGuildTechContributeRequest;
|
||||||
|
|
||||||
interface IGuildTechBasicRequest {
|
interface IGuildTechBasicRequest {
|
||||||
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
|
Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush";
|
||||||
Mode: "Guild" | "Personal";
|
Mode: "Guild" | "Personal";
|
||||||
RecipeType: string;
|
RecipeType: string;
|
||||||
|
TechProductCategory?: string;
|
||||||
|
CategoryItemId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGuildTechBuyRequest {
|
interface IGuildTechBuyRequest extends Omit<IGuildTechBasicRequest, "Action"> {
|
||||||
Action: string;
|
Action: string;
|
||||||
Mode: "Guild";
|
|
||||||
RecipeType: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGuildTechContributeRequest {
|
interface IGuildTechContributeRequest {
|
||||||
@ -321,3 +346,30 @@ interface IGuildTechContributeRequest {
|
|||||||
VaultCredits: number;
|
VaultCredits: number;
|
||||||
VaultMiscItems: IMiscItem[];
|
VaultMiscItems: IMiscItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
|
||||||
|
// delete personal tech project
|
||||||
|
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId));
|
||||||
|
if (personalTechProjectIndex != -1) {
|
||||||
|
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// find salved part & delete it
|
||||||
|
const crewShipSalvagedWeaponSkinsIndex = inventory.CrewShipSalvagedWeaponSkins.findIndex(x => x._id.equals(itemId));
|
||||||
|
const crewShipWeaponSkin = inventory.CrewShipSalvagedWeaponSkins[crewShipSalvagedWeaponSkinsIndex];
|
||||||
|
inventory.CrewShipSalvagedWeaponSkins.splice(crewShipSalvagedWeaponSkinsIndex, 1);
|
||||||
|
|
||||||
|
// add final item
|
||||||
|
const inventoryChanges = {
|
||||||
|
...addCrewShipWeaponSkin(inventory, crewShipWeaponSkin.ItemType, crewShipWeaponSkin.UpgradeFingerprint),
|
||||||
|
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
inventoryChanges.RemovedIdItems = [
|
||||||
|
{
|
||||||
|
ItemId: { $oid: itemId }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return inventoryChanges;
|
||||||
|
};
|
||||||
|
@ -149,7 +149,7 @@ export const getInventoryResponse = async (
|
|||||||
inventoryResponse.ShipDecorations = [];
|
inventoryResponse.ShipDecorations = [];
|
||||||
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
||||||
if (item.productCategory == "ShipDecorations") {
|
if (item.productCategory == "ShipDecorations") {
|
||||||
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 1 });
|
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 999_999 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -258,6 +258,10 @@ export const getInventoryResponse = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.noDailyFocusLimit) {
|
||||||
|
inventoryResponse.DailyFocus = Math.max(999_999, 250000 + inventoryResponse.PlayerLevel * 5000);
|
||||||
|
}
|
||||||
|
|
||||||
if (inventoryResponse.InfestedFoundry) {
|
if (inventoryResponse.InfestedFoundry) {
|
||||||
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,12 @@ import {
|
|||||||
addRecipes,
|
addRecipes,
|
||||||
addMiscItems,
|
addMiscItems,
|
||||||
addConsumables,
|
addConsumables,
|
||||||
freeUpSlot
|
freeUpSlot,
|
||||||
|
combineInventoryChanges
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
export const sellController: RequestHandler = async (req, res) => {
|
export const sellController: RequestHandler = async (req, res) => {
|
||||||
const payload = JSON.parse(String(req.body)) as ISellRequest;
|
const payload = JSON.parse(String(req.body)) as ISellRequest;
|
||||||
@ -48,6 +51,9 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
if (payload.Items.Hoverboards) {
|
if (payload.Items.Hoverboards) {
|
||||||
requiredFields.add(InventorySlot.SPACESUITS);
|
requiredFields.add(InventorySlot.SPACESUITS);
|
||||||
}
|
}
|
||||||
|
if (payload.Items.CrewShipWeapons) {
|
||||||
|
requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
||||||
|
}
|
||||||
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
|
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
|
||||||
|
|
||||||
// Give currency
|
// Give currency
|
||||||
@ -69,10 +75,14 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
ItemCount: payload.SellPrice
|
ItemCount: payload.SellPrice
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
} else if (payload.SellCurrency == "SC_Resources") {
|
||||||
|
// Will add appropriate MiscItems from CrewShipWeapons
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
|
throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
|
||||||
// Remove item(s)
|
// Remove item(s)
|
||||||
if (payload.Items.Suits) {
|
if (payload.Items.Suits) {
|
||||||
payload.Items.Suits.forEach(sellItem => {
|
payload.Items.Suits.forEach(sellItem => {
|
||||||
@ -145,6 +155,24 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
inventory.Drones.pull({ _id: sellItem.String });
|
inventory.Drones.pull({ _id: sellItem.String });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (payload.Items.CrewShipWeapons) {
|
||||||
|
payload.Items.CrewShipWeapons.forEach(sellItem => {
|
||||||
|
const index = inventory.CrewShipWeapons.findIndex(x => x._id.equals(sellItem.String));
|
||||||
|
if (index != -1) {
|
||||||
|
const itemType = inventory.CrewShipWeapons[index].ItemType;
|
||||||
|
const recipe = Object.values(ExportDojoRecipes.fabrications).find(x => x.resultType == itemType)!;
|
||||||
|
const miscItemChanges = recipe.ingredients.map(x => ({
|
||||||
|
ItemType: x.ItemType,
|
||||||
|
ItemCount: Math.trunc(x.ItemCount * 0.8)
|
||||||
|
}));
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
|
||||||
|
|
||||||
|
inventory.CrewShipWeapons.splice(index, 1);
|
||||||
|
freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
if (payload.Items.Consumables) {
|
if (payload.Items.Consumables) {
|
||||||
const consumablesChanges = [];
|
const consumablesChanges = [];
|
||||||
for (const sellItem of payload.Items.Consumables) {
|
for (const sellItem of payload.Items.Consumables) {
|
||||||
@ -191,7 +219,9 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({});
|
res.json({
|
||||||
|
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ISellRequest {
|
interface ISellRequest {
|
||||||
@ -212,6 +242,7 @@ interface ISellRequest {
|
|||||||
OperatorAmps?: ISellItem[];
|
OperatorAmps?: ISellItem[];
|
||||||
Hoverboards?: ISellItem[];
|
Hoverboards?: ISellItem[];
|
||||||
Drones?: ISellItem[];
|
Drones?: ISellItem[];
|
||||||
|
CrewShipWeapons?: ISellItem[];
|
||||||
};
|
};
|
||||||
SellPrice: number;
|
SellPrice: number;
|
||||||
SellCurrency:
|
SellCurrency:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
|
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
|
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
|
||||||
|
|
||||||
@ -7,5 +7,17 @@ export const setPlacedDecoInfoController: RequestHandler = async (req, res) => {
|
|||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest;
|
const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest;
|
||||||
await handleSetPlacedDecoInfo(accountId, payload);
|
await handleSetPlacedDecoInfo(accountId, payload);
|
||||||
res.end();
|
res.json({
|
||||||
|
DecoId: payload.DecoId,
|
||||||
|
IsPicture: true,
|
||||||
|
PictureFrameInfo: payload.PictureFrameInfo,
|
||||||
|
BootLocation: payload.BootLocation
|
||||||
|
} satisfies ISetPlacedDecoInfoResponse);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface ISetPlacedDecoInfoResponse {
|
||||||
|
DecoId: string;
|
||||||
|
IsPicture: boolean;
|
||||||
|
PictureFrameInfo?: IPictureFrameInfo;
|
||||||
|
BootLocation?: string;
|
||||||
|
}
|
||||||
|
@ -25,7 +25,13 @@ export const upgradesController: RequestHandler = async (req, res) => {
|
|||||||
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
|
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
|
||||||
) {
|
) {
|
||||||
updateCurrency(inventory, 10, true);
|
updateCurrency(inventory, 10, true);
|
||||||
} else if (operation.OperationType != "UOT_ABILITY_OVERRIDE") {
|
} else if (
|
||||||
|
operation.OperationType != "UOT_SWAP_POLARITY" &&
|
||||||
|
operation.OperationType != "UOT_ABILITY_OVERRIDE"
|
||||||
|
) {
|
||||||
|
if (!operation.UpgradeRequirement) {
|
||||||
|
throw new Error(`${operation.OperationType} operation should be free?`);
|
||||||
|
}
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
ItemType: operation.UpgradeRequirement,
|
ItemType: operation.UpgradeRequirement,
|
||||||
|
@ -3,6 +3,7 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService"
|
|||||||
import {
|
import {
|
||||||
ExportArcanes,
|
ExportArcanes,
|
||||||
ExportAvionics,
|
ExportAvionics,
|
||||||
|
ExportCustoms,
|
||||||
ExportDrones,
|
ExportDrones,
|
||||||
ExportGear,
|
ExportGear,
|
||||||
ExportKeys,
|
ExportKeys,
|
||||||
@ -171,6 +172,12 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
name: getString(item.name, lang)
|
name: getString(item.name, lang)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
|
||||||
|
res.miscitems.push({
|
||||||
|
uniqueName: uniqueName,
|
||||||
|
name: getString(item.name, lang)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
res.mods = [];
|
res.mods = [];
|
||||||
for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
|
for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
|
||||||
|
@ -56,15 +56,12 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "deleteKey": {
|
case "deleteKey": {
|
||||||
if (allQuestKeys.includes(questItemType)) {
|
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
|
||||||
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
|
if (!questKey) {
|
||||||
if (!questKey) {
|
logger.error(`Quest key not found in inventory: ${questItemType}`);
|
||||||
logger.error(`Quest key not found in inventory: ${questItemType}`);
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
inventory.QuestKeys.pull({ ItemType: questItemType });
|
|
||||||
}
|
}
|
||||||
|
inventory.QuestKeys.pull({ ItemType: questItemType });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "completeKey": {
|
case "completeKey": {
|
||||||
|
@ -504,6 +504,8 @@ const personalTechProjectSchema = new Schema<IPersonalTechProjectDatabase>({
|
|||||||
State: Number,
|
State: Number,
|
||||||
ReqCredits: Number,
|
ReqCredits: Number,
|
||||||
ItemType: String,
|
ItemType: String,
|
||||||
|
ProductCategory: String,
|
||||||
|
CategoryItemId: Schema.Types.ObjectId,
|
||||||
ReqItems: { type: [typeCountSchema], default: undefined },
|
ReqItems: { type: [typeCountSchema], default: undefined },
|
||||||
HasContributions: Boolean,
|
HasContributions: Boolean,
|
||||||
CompletionDate: Date
|
CompletionDate: Date
|
||||||
@ -522,6 +524,9 @@ personalTechProjectSchema.set("toJSON", {
|
|||||||
const db = ret as IPersonalTechProjectDatabase;
|
const db = ret as IPersonalTechProjectDatabase;
|
||||||
const client = ret as IPersonalTechProjectClient;
|
const client = ret as IPersonalTechProjectClient;
|
||||||
|
|
||||||
|
if (db.CategoryItemId) {
|
||||||
|
client.CategoryItemId = toOid(db.CategoryItemId);
|
||||||
|
}
|
||||||
if (db.CompletionDate) {
|
if (db.CompletionDate) {
|
||||||
client.CompletionDate = toMongoDate(db.CompletionDate);
|
client.CompletionDate = toMongoDate(db.CompletionDate);
|
||||||
}
|
}
|
||||||
@ -1213,7 +1218,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
accountOwnerId: Schema.Types.ObjectId,
|
accountOwnerId: Schema.Types.ObjectId,
|
||||||
SubscribedToEmails: { type: Number, default: 0 },
|
SubscribedToEmails: { type: Number, default: 0 },
|
||||||
SubscribedToEmailsPersonalized: { type: Number, default: 0 },
|
SubscribedToEmailsPersonalized: { type: Number, default: 0 },
|
||||||
RewardSeed: Number,
|
RewardSeed: BigInt,
|
||||||
|
|
||||||
//Credit
|
//Credit
|
||||||
RegularCredits: { type: Number, default: 0 },
|
RegularCredits: { type: Number, default: 0 },
|
||||||
@ -1613,7 +1618,7 @@ export type InventoryDocumentProps = {
|
|||||||
QuestKeys: Types.DocumentArray<IQuestKeyDatabase>;
|
QuestKeys: Types.DocumentArray<IQuestKeyDatabase>;
|
||||||
Drones: Types.DocumentArray<IDroneDatabase>;
|
Drones: Types.DocumentArray<IDroneDatabase>;
|
||||||
CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
|
CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
|
||||||
CrewShipSalvagedWeaponsSkins: Types.DocumentArray<IUpgradeDatabase>;
|
CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
|
||||||
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
|
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
|
||||||
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
|
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import { contributeToVaultController } from "@/src/controllers/api/contributeToV
|
|||||||
import { createAllianceController } from "@/src/controllers/api/createAllianceController";
|
import { createAllianceController } from "@/src/controllers/api/createAllianceController";
|
||||||
import { createGuildController } from "@/src/controllers/api/createGuildController";
|
import { createGuildController } from "@/src/controllers/api/createGuildController";
|
||||||
import { creditsController } from "@/src/controllers/api/creditsController";
|
import { creditsController } from "@/src/controllers/api/creditsController";
|
||||||
|
import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
|
||||||
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
|
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
|
||||||
import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
|
import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
|
||||||
import { declineAllianceInviteController } from "@/src/controllers/api/declineAllianceInviteController";
|
import { declineAllianceInviteController } from "@/src/controllers/api/declineAllianceInviteController";
|
||||||
@ -61,7 +62,8 @@ import { giftingController } from "@/src/controllers/api/giftingController";
|
|||||||
import { gildWeaponController } from "@/src/controllers/api/gildWeaponController";
|
import { gildWeaponController } from "@/src/controllers/api/gildWeaponController";
|
||||||
import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
|
import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
|
||||||
import { giveKeyChainTriggeredMessageController } from "@/src/controllers/api/giveKeyChainTriggeredMessageController";
|
import { giveKeyChainTriggeredMessageController } from "@/src/controllers/api/giveKeyChainTriggeredMessageController";
|
||||||
import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKey";
|
import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKeyRewardController";
|
||||||
|
import { giveShipDecoAndLoreFragmentController } from "@/src/controllers/api/giveShipDecoAndLoreFragmentController";
|
||||||
import { giveStartingGearController } from "@/src/controllers/api/giveStartingGearController";
|
import { giveStartingGearController } from "@/src/controllers/api/giveStartingGearController";
|
||||||
import { guildTechController } from "@/src/controllers/api/guildTechController";
|
import { guildTechController } from "@/src/controllers/api/guildTechController";
|
||||||
import { hostSessionController } from "@/src/controllers/api/hostSessionController";
|
import { hostSessionController } from "@/src/controllers/api/hostSessionController";
|
||||||
@ -217,6 +219,7 @@ apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentContro
|
|||||||
apiRouter.post("/contributeToVault.php", contributeToVaultController);
|
apiRouter.post("/contributeToVault.php", contributeToVaultController);
|
||||||
apiRouter.post("/createAlliance.php", createAllianceController);
|
apiRouter.post("/createAlliance.php", createAllianceController);
|
||||||
apiRouter.post("/createGuild.php", createGuildController);
|
apiRouter.post("/createGuild.php", createGuildController);
|
||||||
|
apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
|
||||||
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
|
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
|
||||||
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
|
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
|
||||||
apiRouter.post("/destroyDojoDeco.php", destroyDojoDecoController);
|
apiRouter.post("/destroyDojoDeco.php", destroyDojoDecoController);
|
||||||
@ -239,6 +242,7 @@ apiRouter.post("/gildWeapon.php", gildWeaponController);
|
|||||||
apiRouter.post("/giveKeyChainTriggeredItems.php", giveKeyChainTriggeredItemsController);
|
apiRouter.post("/giveKeyChainTriggeredItems.php", giveKeyChainTriggeredItemsController);
|
||||||
apiRouter.post("/giveKeyChainTriggeredMessage.php", giveKeyChainTriggeredMessageController);
|
apiRouter.post("/giveKeyChainTriggeredMessage.php", giveKeyChainTriggeredMessageController);
|
||||||
apiRouter.post("/giveQuestKeyReward.php", giveQuestKeyRewardController);
|
apiRouter.post("/giveQuestKeyReward.php", giveQuestKeyRewardController);
|
||||||
|
apiRouter.post("/giveShipDecoAndLoreFragment.php", giveShipDecoAndLoreFragmentController);
|
||||||
apiRouter.post("/giveStartingGear.php", giveStartingGearController);
|
apiRouter.post("/giveStartingGear.php", giveStartingGearController);
|
||||||
apiRouter.post("/guildTech.php", guildTechController);
|
apiRouter.post("/guildTech.php", guildTechController);
|
||||||
apiRouter.post("/hostSession.php", hostSessionController);
|
apiRouter.post("/hostSession.php", hostSessionController);
|
||||||
|
@ -35,6 +35,7 @@ interface IConfig {
|
|||||||
unlockExilusEverywhere?: boolean;
|
unlockExilusEverywhere?: boolean;
|
||||||
unlockArcanesEverywhere?: boolean;
|
unlockArcanesEverywhere?: boolean;
|
||||||
noDailyStandingLimits?: boolean;
|
noDailyStandingLimits?: boolean;
|
||||||
|
noDailyFocusLimit?: boolean;
|
||||||
noArgonCrystalDecay?: boolean;
|
noArgonCrystalDecay?: boolean;
|
||||||
noMasteryRankUpCooldown?: boolean;
|
noMasteryRankUpCooldown?: boolean;
|
||||||
noVendorPurchaseLimits?: boolean;
|
noVendorPurchaseLimits?: boolean;
|
||||||
|
@ -31,6 +31,7 @@ import { IFusionTreasure, ITypeCount } from "../types/inventoryTypes/inventoryTy
|
|||||||
import { IInventoryChanges } from "../types/purchaseTypes";
|
import { IInventoryChanges } from "../types/purchaseTypes";
|
||||||
import { parallelForeach } from "../utils/async-utils";
|
import { parallelForeach } from "../utils/async-utils";
|
||||||
import allDecoRecipes from "@/static/fixed_responses/allDecoRecipes.json";
|
import allDecoRecipes from "@/static/fixed_responses/allDecoRecipes.json";
|
||||||
|
import { createMessage } from "./inboxService";
|
||||||
|
|
||||||
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
|
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -601,6 +602,50 @@ const setGuildTier = async (guild: TGuildDatabaseDocument, newTier: number): Pro
|
|||||||
await processGuildTechProjectContributionsUpdate(guild, project);
|
await processGuildTechProjectContributionsUpdate(guild, project);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (guild.CeremonyContributors) {
|
||||||
|
await checkClanAscensionHasRequiredContributors(guild);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkClanAscensionHasRequiredContributors = async (guild: TGuildDatabaseDocument): Promise<void> => {
|
||||||
|
const requiredContributors = [1, 5, 15, 30, 50][guild.Tier - 1];
|
||||||
|
// Once required contributor count is hit, the class is committed and there's 72 hours to claim endo.
|
||||||
|
if (guild.CeremonyContributors!.length >= requiredContributors) {
|
||||||
|
guild.Class = guild.CeremonyClass!;
|
||||||
|
guild.CeremonyClass = undefined;
|
||||||
|
guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000));
|
||||||
|
if (!config.fastClanAscension) {
|
||||||
|
// Send message to all active guild members
|
||||||
|
const members = await GuildMember.find({ guildId: guild._id, status: 0 }, "accountId");
|
||||||
|
await parallelForeach(members, async member => {
|
||||||
|
// somewhat unfaithful as on live the "msg" is not a loctag, but since we don't have the string, we'll let the client fill it in with "arg".
|
||||||
|
await createMessage(member.accountId, [
|
||||||
|
{
|
||||||
|
sndr: guild.Name,
|
||||||
|
msg: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgressDetails",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "RESETDATE",
|
||||||
|
Tag:
|
||||||
|
guild.CeremonyResetDate!.getUTCMonth() +
|
||||||
|
"/" +
|
||||||
|
guild.CeremonyResetDate!.getUTCDate() +
|
||||||
|
"/" +
|
||||||
|
(guild.CeremonyResetDate!.getUTCFullYear() % 100) +
|
||||||
|
" " +
|
||||||
|
guild.CeremonyResetDate!.getUTCHours().toString().padStart(2, "0") +
|
||||||
|
":" +
|
||||||
|
guild.CeremonyResetDate!.getUTCMinutes().toString().padStart(2, "0")
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgress",
|
||||||
|
icon: "/Lotus/Interface/Graphics/ClanTileImages/ClanEnterDojo.png",
|
||||||
|
highPriority: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeDojoKeyItems = (inventory: TInventoryDatabaseDocument): IInventoryChanges => {
|
export const removeDojoKeyItems = (inventory: TInventoryDatabaseDocument): IInventoryChanges => {
|
||||||
|
@ -21,7 +21,8 @@ import {
|
|||||||
ICalendarProgress,
|
ICalendarProgress,
|
||||||
IDroneClient,
|
IDroneClient,
|
||||||
IUpgradeClient,
|
IUpgradeClient,
|
||||||
TPartialStartingGear
|
TPartialStartingGear,
|
||||||
|
ILoreFragmentScan
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
|
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
|
||||||
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
|
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
|
||||||
@ -407,8 +408,32 @@ export const addItem = async (
|
|||||||
const meta = ExportCustoms[typeName];
|
const meta = ExportCustoms[typeName];
|
||||||
let inventoryChanges: IInventoryChanges;
|
let inventoryChanges: IInventoryChanges;
|
||||||
if (meta.productCategory == "CrewShipWeaponSkins") {
|
if (meta.productCategory == "CrewShipWeaponSkins") {
|
||||||
inventoryChanges = addCrewShipWeaponSkin(inventory, typeName);
|
if (meta.subroutines || meta.randomisedUpgrades) {
|
||||||
|
// House versions need to be identified to get stats so put them into raw salvage first.
|
||||||
|
const rawSalvageChanges = [
|
||||||
|
{
|
||||||
|
ItemType: typeName,
|
||||||
|
ItemCount: quantity
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addCrewShipRawSalvage(inventory, rawSalvageChanges);
|
||||||
|
inventoryChanges = { CrewShipRawSalvage: rawSalvageChanges };
|
||||||
|
} else {
|
||||||
|
// Sigma versions can be added directly.
|
||||||
|
if (quantity != 1) {
|
||||||
|
throw new Error(
|
||||||
|
`unexpected acquisition quantity of CrewShipWeaponSkin: got ${quantity}, expected 1`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
inventoryChanges = {
|
||||||
|
...addCrewShipWeaponSkin(inventory, typeName, undefined),
|
||||||
|
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, premiumPurchase)
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (quantity != 1) {
|
||||||
|
throw new Error(`unexpected acquisition quantity of WeaponSkins: got ${quantity}, expected 1`);
|
||||||
|
}
|
||||||
inventoryChanges = addSkin(inventory, typeName);
|
inventoryChanges = addSkin(inventory, typeName);
|
||||||
}
|
}
|
||||||
if (meta.additionalItems) {
|
if (meta.additionalItems) {
|
||||||
@ -1082,12 +1107,14 @@ export const addSkin = (
|
|||||||
return inventoryChanges;
|
return inventoryChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addCrewShipWeaponSkin = (
|
export const addCrewShipWeaponSkin = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
typeName: string,
|
typeName: string,
|
||||||
|
upgradeFingerprint: string | undefined,
|
||||||
inventoryChanges: IInventoryChanges = {}
|
inventoryChanges: IInventoryChanges = {}
|
||||||
): IInventoryChanges => {
|
): IInventoryChanges => {
|
||||||
const index = inventory.CrewShipWeaponSkins.push({ ItemType: typeName }) - 1;
|
const index =
|
||||||
|
inventory.CrewShipWeaponSkins.push({ ItemType: typeName, UpgradeFingerprint: upgradeFingerprint }) - 1;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
inventoryChanges.CrewShipWeaponSkins ??= [];
|
inventoryChanges.CrewShipWeaponSkins ??= [];
|
||||||
(inventoryChanges.CrewShipWeaponSkins as IUpgradeClient[]).push(
|
(inventoryChanges.CrewShipWeaponSkins as IUpgradeClient[]).push(
|
||||||
@ -1096,6 +1123,22 @@ const addCrewShipWeaponSkin = (
|
|||||||
return inventoryChanges;
|
return inventoryChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const addCrewShipSalvagedWeaponSkin = (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
typeName: string,
|
||||||
|
upgradeFingerprint: string | undefined,
|
||||||
|
inventoryChanges: IInventoryChanges = {}
|
||||||
|
): IInventoryChanges => {
|
||||||
|
const index =
|
||||||
|
inventory.CrewShipSalvagedWeaponSkins.push({ ItemType: typeName, UpgradeFingerprint: upgradeFingerprint }) - 1;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
inventoryChanges.CrewShipSalvagedWeaponSkins ??= [];
|
||||||
|
(inventoryChanges.CrewShipSalvagedWeaponSkins as IUpgradeClient[]).push(
|
||||||
|
inventory.CrewShipSalvagedWeaponSkins[index].toJSON<IUpgradeClient>()
|
||||||
|
);
|
||||||
|
return inventoryChanges;
|
||||||
|
};
|
||||||
|
|
||||||
const addCrewShip = (
|
const addCrewShip = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
typeName: string,
|
typeName: string,
|
||||||
@ -1347,7 +1390,20 @@ export const addFocusXpIncreases = (inventory: TInventoryDatabaseDocument, focus
|
|||||||
inventory.FocusXP.AP_POWER += focusXpPlus[FocusType.AP_POWER];
|
inventory.FocusXP.AP_POWER += focusXpPlus[FocusType.AP_POWER];
|
||||||
inventory.FocusXP.AP_WARD += focusXpPlus[FocusType.AP_WARD];
|
inventory.FocusXP.AP_WARD += focusXpPlus[FocusType.AP_WARD];
|
||||||
|
|
||||||
inventory.DailyFocus -= focusXpPlus.reduce((a, b) => a + b, 0);
|
if (!config.noDailyFocusLimit) {
|
||||||
|
inventory.DailyFocus -= focusXpPlus.reduce((a, b) => a + b, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addLoreFragmentScans = (inventory: TInventoryDatabaseDocument, arr: ILoreFragmentScan[]): void => {
|
||||||
|
arr.forEach(clientFragment => {
|
||||||
|
const fragment = inventory.LoreFragmentScans.find(x => x.ItemType == clientFragment.ItemType);
|
||||||
|
if (fragment) {
|
||||||
|
fragment.Progress += clientFragment.Progress;
|
||||||
|
} else {
|
||||||
|
inventory.LoreFragmentScans.push(clientFragment);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addChallenges = (
|
export const addChallenges = (
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IRngResult, getRandomElement, getRandomReward } from "@/src/services/rngService";
|
import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
|
||||||
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import {
|
import {
|
||||||
addBooster,
|
addBooster,
|
||||||
@ -23,6 +23,7 @@ import {
|
|||||||
addGearExpByCategory,
|
addGearExpByCategory,
|
||||||
addItem,
|
addItem,
|
||||||
addLevelKeys,
|
addLevelKeys,
|
||||||
|
addLoreFragmentScans,
|
||||||
addMiscItems,
|
addMiscItems,
|
||||||
addMissionComplete,
|
addMissionComplete,
|
||||||
addMods,
|
addMods,
|
||||||
@ -30,6 +31,7 @@ import {
|
|||||||
addShipDecorations,
|
addShipDecorations,
|
||||||
addStanding,
|
addStanding,
|
||||||
combineInventoryChanges,
|
combineInventoryChanges,
|
||||||
|
generateRewardSeed,
|
||||||
updateCurrency,
|
updateCurrency,
|
||||||
updateSyndicate
|
updateSyndicate
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
@ -53,7 +55,22 @@ import { Loadout } from "../models/inventoryModels/loadoutModel";
|
|||||||
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
|
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
|
||||||
import { getWorldState } from "./worldStateService";
|
import { getWorldState } from "./worldStateService";
|
||||||
|
|
||||||
const getRotations = (rotationCount: number, tierOverride: number | undefined): number[] => {
|
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
|
||||||
|
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
||||||
|
if (rewardInfo.VaultsCracked) {
|
||||||
|
const rotations: number[] = [];
|
||||||
|
for (let i = 0; i != rewardInfo.VaultsCracked; ++i) {
|
||||||
|
rotations.push(i);
|
||||||
|
}
|
||||||
|
return rotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Rescue missions
|
||||||
|
if (rewardInfo.node in ExportRegions && ExportRegions[rewardInfo.node].missionIndex == 3 && rewardInfo.rewardTier) {
|
||||||
|
return [rewardInfo.rewardTier];
|
||||||
|
}
|
||||||
|
|
||||||
|
const rotationCount = rewardInfo.rewardQualifications?.length || 0;
|
||||||
if (rotationCount === 0) return [0];
|
if (rotationCount === 0) return [0];
|
||||||
|
|
||||||
const rotationPattern =
|
const rotationPattern =
|
||||||
@ -69,7 +86,12 @@ const getRotations = (rotationCount: number, tierOverride: number | undefined):
|
|||||||
return rotatedValues;
|
return rotatedValues;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
|
const getRandomRewardByChance = (pool: IReward[], rng?: SRng): IRngResult | undefined => {
|
||||||
|
if (rng) {
|
||||||
|
const res = rng.randomReward(pool as IRngResult[]);
|
||||||
|
rng.randomFloat(); // something related to rewards multiplier
|
||||||
|
return res;
|
||||||
|
}
|
||||||
return getRandomReward(pool as IRngResult[]);
|
return getRandomReward(pool as IRngResult[]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -291,14 +313,7 @@ export const addMissionInventoryUpdates = async (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "LoreFragmentScans":
|
case "LoreFragmentScans":
|
||||||
value.forEach(clientFragment => {
|
addLoreFragmentScans(inventory, value);
|
||||||
const fragment = inventory.LoreFragmentScans.find(x => x.ItemType == clientFragment.ItemType);
|
|
||||||
if (fragment) {
|
|
||||||
fragment.Progress += clientFragment.Progress;
|
|
||||||
} else {
|
|
||||||
inventory.LoreFragmentScans.push(clientFragment);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case "LibraryScans":
|
case "LibraryScans":
|
||||||
value.forEach(scan => {
|
value.forEach(scan => {
|
||||||
@ -554,6 +569,11 @@ export const addMissionRewards = async (
|
|||||||
return { MissionRewards: [] };
|
return { MissionRewards: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rewardInfo.rewardSeed) {
|
||||||
|
// We're using a reward seed, so give the client a new one in the response. On live, missionInventoryUpdate seems to always provide a fresh one in the response.
|
||||||
|
inventory.RewardSeed = generateRewardSeed();
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: check double reward merging
|
//TODO: check double reward merging
|
||||||
const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo, wagerTier);
|
const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo, wagerTier);
|
||||||
logger.debug("random mission drops:", MissionRewards);
|
logger.debug("random mission drops:", MissionRewards);
|
||||||
@ -567,7 +587,7 @@ export const addMissionRewards = async (
|
|||||||
const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
|
const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
|
||||||
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
|
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
|
||||||
if (fixedLevelRewards.levelKeyRewards) {
|
if (fixedLevelRewards.levelKeyRewards) {
|
||||||
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards);
|
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards, rewardInfo);
|
||||||
}
|
}
|
||||||
if (fixedLevelRewards.levelKeyRewards2) {
|
if (fixedLevelRewards.levelKeyRewards2) {
|
||||||
for (const reward of fixedLevelRewards.levelKeyRewards2) {
|
for (const reward of fixedLevelRewards.levelKeyRewards2) {
|
||||||
@ -592,7 +612,14 @@ export const addMissionRewards = async (
|
|||||||
const node = ExportRegions[missions.Tag];
|
const node = ExportRegions[missions.Tag];
|
||||||
|
|
||||||
//node based credit rewards for mission completion
|
//node based credit rewards for mission completion
|
||||||
if (node.missionIndex !== 28) {
|
if (
|
||||||
|
node.missionIndex != 23 && // junction
|
||||||
|
node.missionIndex != 28 && // open world
|
||||||
|
missions.Tag != "SolNode761" && // the index
|
||||||
|
missions.Tag != "SolNode762" && // the index
|
||||||
|
missions.Tag != "SolNode763" && // the index
|
||||||
|
missions.Tag != "CrewBattleNode556" // free flight
|
||||||
|
) {
|
||||||
const levelCreditReward = getLevelCreditRewards(node);
|
const levelCreditReward = getLevelCreditRewards(node);
|
||||||
missionCompletionCredits += levelCreditReward;
|
missionCompletionCredits += levelCreditReward;
|
||||||
inventory.RegularCredits += levelCreditReward;
|
inventory.RegularCredits += levelCreditReward;
|
||||||
@ -600,7 +627,20 @@ export const addMissionRewards = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node.missionReward) {
|
if (node.missionReward) {
|
||||||
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards);
|
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missions.Tag == "PlutoToErisJunction") {
|
||||||
|
await createMessage(inventory.accountOwnerId, [
|
||||||
|
{
|
||||||
|
sndr: "/Lotus/Language/G1Quests/GolemQuestJordasName",
|
||||||
|
msg: "/Lotus/Language/G1Quests/GolemQuestIntroBody",
|
||||||
|
att: ["/Lotus/Types/Keys/GolemQuest/GolemQuestKeyChainItem"],
|
||||||
|
sub: "/Lotus/Language/G1Quests/GolemQuestIntroTitle",
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/JordasPortrait.png",
|
||||||
|
highPriority: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -711,14 +751,12 @@ export const addMissionRewards = async (
|
|||||||
|
|
||||||
if (rewardInfo.JobStage != undefined && rewardInfo.jobId) {
|
if (rewardInfo.JobStage != undefined && rewardInfo.jobId) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [jobType, tierStr, hubNode, syndicateId, locationTag] = rewardInfo.jobId.split("_");
|
const [jobType, unkIndex, hubNode, syndicateId, locationTag] = rewardInfo.jobId.split("_");
|
||||||
const tier = Number(tierStr);
|
|
||||||
|
|
||||||
const worldState = getWorldState();
|
const worldState = getWorldState();
|
||||||
let syndicateEntry = worldState.SyndicateMissions.find(m => m._id.$oid === syndicateId);
|
let syndicateEntry = worldState.SyndicateMissions.find(m => m._id.$oid === syndicateId);
|
||||||
if (!syndicateEntry) syndicateEntry = worldState.SyndicateMissions.find(m => m.Tag === syndicateId); // Sometimes syndicateId can be tag
|
if (!syndicateEntry) syndicateEntry = worldState.SyndicateMissions.find(m => m.Tag === syndicateId); // Sometimes syndicateId can be tag
|
||||||
if (syndicateEntry && syndicateEntry.Jobs) {
|
if (syndicateEntry && syndicateEntry.Jobs) {
|
||||||
let currentJob = syndicateEntry.Jobs[tier];
|
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
|
||||||
if (syndicateEntry.Tag === "EntratiSyndicate") {
|
if (syndicateEntry.Tag === "EntratiSyndicate") {
|
||||||
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
|
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
|
||||||
if (vault) currentJob = vault;
|
if (vault) currentJob = vault;
|
||||||
@ -743,7 +781,7 @@ export const addMissionRewards = async (
|
|||||||
});
|
});
|
||||||
SyndicateXPItemReward = medallionAmount;
|
SyndicateXPItemReward = medallionAmount;
|
||||||
} else {
|
} else {
|
||||||
if (tier >= 0) {
|
if (rewardInfo.JobTier! >= 0) {
|
||||||
AffiliationMods.push(
|
AffiliationMods.push(
|
||||||
addStanding(inventory, syndicateEntry.Tag, currentJob.xpAmounts[rewardInfo.JobStage])
|
addStanding(inventory, syndicateEntry.Tag, currentJob.xpAmounts[rewardInfo.JobStage])
|
||||||
);
|
);
|
||||||
@ -843,7 +881,8 @@ export const addCredits = (
|
|||||||
export const addFixedLevelRewards = (
|
export const addFixedLevelRewards = (
|
||||||
rewards: IMissionRewardExternal,
|
rewards: IMissionRewardExternal,
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
MissionRewards: IMissionReward[]
|
MissionRewards: IMissionReward[],
|
||||||
|
rewardInfo?: IRewardInfo
|
||||||
): number => {
|
): number => {
|
||||||
let missionBonusCredits = 0;
|
let missionBonusCredits = 0;
|
||||||
if (rewards.credits) {
|
if (rewards.credits) {
|
||||||
@ -873,13 +912,16 @@ export const addFixedLevelRewards = (
|
|||||||
}
|
}
|
||||||
if (rewards.droptable) {
|
if (rewards.droptable) {
|
||||||
if (rewards.droptable in ExportRewards) {
|
if (rewards.droptable in ExportRewards) {
|
||||||
logger.debug(`rolling ${rewards.droptable} for level key rewards`);
|
const rotations: number[] = rewardInfo ? getRotations(rewardInfo) : [0];
|
||||||
const reward = getRandomRewardByChance(ExportRewards[rewards.droptable][0]);
|
logger.debug(`rolling ${rewards.droptable} for level key rewards`, { rotations });
|
||||||
if (reward) {
|
for (const tier of rotations) {
|
||||||
MissionRewards.push({
|
const reward = getRandomRewardByChance(ExportRewards[rewards.droptable][tier]);
|
||||||
StoreItem: reward.type,
|
if (reward) {
|
||||||
ItemCount: reward.itemCount
|
MissionRewards.push({
|
||||||
});
|
StoreItem: reward.type,
|
||||||
|
ItemCount: reward.itemCount
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.error(`unknown droptable ${rewards.droptable}`);
|
logger.error(`unknown droptable ${rewards.droptable}`);
|
||||||
@ -898,6 +940,12 @@ function getLevelCreditRewards(node: IRegion): number {
|
|||||||
|
|
||||||
function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | undefined): IMissionReward[] {
|
function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | undefined): IMissionReward[] {
|
||||||
const drops: IMissionReward[] = [];
|
const drops: IMissionReward[] = [];
|
||||||
|
if (RewardInfo.periodicMissionTag?.startsWith("HardDaily")) {
|
||||||
|
drops.push({
|
||||||
|
StoreItem: "/Lotus/StoreItems/Types/Items/MiscItems/SteelEssence",
|
||||||
|
ItemCount: 5
|
||||||
|
});
|
||||||
|
}
|
||||||
if (RewardInfo.node in ExportRegions) {
|
if (RewardInfo.node in ExportRegions) {
|
||||||
const region = ExportRegions[RewardInfo.node];
|
const region = ExportRegions[RewardInfo.node];
|
||||||
let rewardManifests: string[] =
|
let rewardManifests: string[] =
|
||||||
@ -909,8 +957,7 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
|
|||||||
if (RewardInfo.jobId) {
|
if (RewardInfo.jobId) {
|
||||||
if (RewardInfo.JobStage! >= 0) {
|
if (RewardInfo.JobStage! >= 0) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [jobType, tierStr, hubNode, syndicateId, locationTag] = RewardInfo.jobId.split("_");
|
const [jobType, unkIndex, hubNode, syndicateId, locationTag] = RewardInfo.jobId.split("_");
|
||||||
const tier = Number(tierStr);
|
|
||||||
let isEndlessJob = false;
|
let isEndlessJob = false;
|
||||||
if (syndicateId) {
|
if (syndicateId) {
|
||||||
const worldState = getWorldState();
|
const worldState = getWorldState();
|
||||||
@ -918,11 +965,11 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
|
|||||||
if (!syndicateEntry) syndicateEntry = worldState.SyndicateMissions.find(m => m.Tag === syndicateId);
|
if (!syndicateEntry) syndicateEntry = worldState.SyndicateMissions.find(m => m.Tag === syndicateId);
|
||||||
|
|
||||||
if (syndicateEntry && syndicateEntry.Jobs) {
|
if (syndicateEntry && syndicateEntry.Jobs) {
|
||||||
let job = syndicateEntry.Jobs[tier];
|
let job = syndicateEntry.Jobs[RewardInfo.JobTier!];
|
||||||
|
|
||||||
if (syndicateEntry.Tag === "EntratiSyndicate") {
|
if (syndicateEntry.Tag === "EntratiSyndicate") {
|
||||||
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
|
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
|
||||||
if (vault) job = vault;
|
if (vault && locationTag) job = vault;
|
||||||
// if (
|
// if (
|
||||||
// [
|
// [
|
||||||
// "DeimosRuinsExterminateBounty",
|
// "DeimosRuinsExterminateBounty",
|
||||||
@ -997,8 +1044,11 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
|
|||||||
(RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) &&
|
(RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) &&
|
||||||
!isEndlessJob
|
!isEndlessJob
|
||||||
) {
|
) {
|
||||||
rewardManifests.push(job.rewards);
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
rotations.push(ExportRewards[job.rewards].length - 1);
|
if (ExportRewards[job.rewards]) {
|
||||||
|
rewardManifests.push(job.rewards);
|
||||||
|
rotations.push(ExportRewards[job.rewards].length - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1041,29 +1091,28 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
|
|||||||
} else {
|
} else {
|
||||||
logger.error(`Unknown syndicate or tier: ${RewardInfo.challengeMissionId}`);
|
logger.error(`Unknown syndicate or tier: ${RewardInfo.challengeMissionId}`);
|
||||||
}
|
}
|
||||||
} else if (RewardInfo.VaultsCracked) {
|
|
||||||
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
|
||||||
for (let i = 0; i != RewardInfo.VaultsCracked; ++i) {
|
|
||||||
rotations.push(i);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const rotationCount = RewardInfo.rewardQualifications?.length || 0;
|
rotations = getRotations(RewardInfo, tierOverride);
|
||||||
rotations = getRotations(rotationCount, tierOverride);
|
|
||||||
}
|
}
|
||||||
if (rewardManifests.length != 0) {
|
if (rewardManifests.length != 0) {
|
||||||
logger.debug(`generating random mission rewards`, { rewardManifests, rotations });
|
logger.debug(`generating random mission rewards`, { rewardManifests, rotations });
|
||||||
}
|
}
|
||||||
rewardManifests
|
const rng = new SRng(BigInt(RewardInfo.rewardSeed ?? generateRewardSeed()) ^ 0xffffffffffffffffn);
|
||||||
.map(name => ExportRewards[name])
|
rewardManifests.forEach(name => {
|
||||||
.forEach(table => {
|
const table = ExportRewards[name];
|
||||||
for (const rotation of rotations) {
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
const rotationRewards = table[rotation];
|
if (!table) {
|
||||||
const drop = getRandomRewardByChance(rotationRewards);
|
logger.error(`unknown droptable: ${name}`);
|
||||||
if (drop) {
|
return;
|
||||||
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
|
}
|
||||||
}
|
for (const rotation of rotations) {
|
||||||
|
const rotationRewards = table[rotation];
|
||||||
|
const drop = getRandomRewardByChance(rotationRewards, rng);
|
||||||
|
if (drop) {
|
||||||
|
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) {
|
if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) {
|
||||||
const deck = ExportRewards[region.cacheRewardManifest];
|
const deck = ExportRewards[region.cacheRewardManifest];
|
||||||
@ -1092,6 +1141,32 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
|
|||||||
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
|
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (RewardInfo.PurgatoryRewardQualifications) {
|
||||||
|
for (const encodedQualification of RewardInfo.PurgatoryRewardQualifications) {
|
||||||
|
const qualification = parseInt(encodedQualification) - 1;
|
||||||
|
if (qualification < 0 || qualification > 8) {
|
||||||
|
logger.error(`unexpected purgatory reward qualification: ${qualification}`);
|
||||||
|
} else {
|
||||||
|
const drop = getRandomRewardByChance(
|
||||||
|
ExportRewards[
|
||||||
|
[
|
||||||
|
"/Lotus/Types/Game/MissionDecks/PurgatoryMissionRewards/PurgatoryBlackTokenRewards",
|
||||||
|
"/Lotus/Types/Game/MissionDecks/PurgatoryMissionRewards/PurgatoryGoldTokenRewards",
|
||||||
|
"/Lotus/Types/Game/MissionDecks/PurgatoryMissionRewards/PurgatoryBlueTokenRewards"
|
||||||
|
][Math.trunc(qualification / 3)]
|
||||||
|
][qualification % 3]
|
||||||
|
);
|
||||||
|
if (drop) {
|
||||||
|
drops.push({
|
||||||
|
StoreItem: drop.type,
|
||||||
|
ItemCount: drop.itemCount,
|
||||||
|
FromEnemyCache: true // to show "identified"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return drops;
|
return drops;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ const getRewardAtPercentage = <T extends { probability: number }>(pool: T[], per
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error("What the fuck?");
|
return pool[pool.length - 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRandomReward = <T extends { probability: number }>(pool: T[]): T | undefined => {
|
export const getRandomReward = <T extends { probability: number }>(pool: T[]): T | undefined => {
|
||||||
@ -142,4 +142,8 @@ export class SRng {
|
|||||||
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
|
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
|
||||||
return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645;
|
return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
||||||
|
return getRewardAtPercentage(pool, this.randomFloat());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,7 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
|
|||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { CRng } from "@/src/services/rngService";
|
import { CRng } from "@/src/services/rngService";
|
||||||
import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus";
|
import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus";
|
||||||
import { ISeasonChallenge, IWorldState } from "../types/worldStateTypes";
|
import { ISeasonChallenge, ISortie, IWorldState } from "../types/worldStateTypes";
|
||||||
import { logger } from "../utils/logger";
|
|
||||||
|
|
||||||
const sortieBosses = [
|
const sortieBosses = [
|
||||||
"SORTIE_BOSS_HYENA",
|
"SORTIE_BOSS_HYENA",
|
||||||
@ -80,77 +79,84 @@ const sortieBossNode: Record<string, string> = {
|
|||||||
SORTIE_BOSS_INFALAD: "SolNode705"
|
SORTIE_BOSS_INFALAD: "SolNode705"
|
||||||
};
|
};
|
||||||
|
|
||||||
const jobSets: string[][] = [
|
const eidolonJobs = [
|
||||||
[
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyLib",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyLib",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyCap",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyCap",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyExt",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyExt",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyCap",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyCap",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyTheft",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyTheft",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyCache",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyCache",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/CaptureBountyCapOne",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/CaptureBountyCapOne",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/CaptureBountyCapTwo",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/CaptureBountyCapTwo",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/SabotageBountySab",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/SabotageBountySab",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc"
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc"
|
];
|
||||||
],
|
|
||||||
[
|
const eidolonNarmerJobs = [
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AssassinateBountyAss",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AssassinateBountyAss",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib"
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib"
|
||||||
],
|
];
|
||||||
[
|
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
|
const venusJobs = [
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusChaosJobAssassinate",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusChaosJobExcavation",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusChaosJobAssassinate",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobAssassinate",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusChaosJobExcavation",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobExterminate",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobAssassinate",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobResource",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobExterminate",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobRecovery",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobResource",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobResource",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobRecovery",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobSpy",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobResource",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusSpyJobSpy",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobSpy",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusTheftJobAmbush",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusSpyJobSpy",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusTheftJobExcavation",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusTheftJobAmbush",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusTheftJobResource",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusTheftJobExcavation",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobCaches",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusTheftJobResource",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobResource",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobCaches",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobSpy",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobResource",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusPreservationJobDefense",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobSpy",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusPreservationJobRecovery",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusPreservationJobDefense",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusPreservationJobResource",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusPreservationJobRecovery",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobAssassinate",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusPreservationJobResource",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobSpy"
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobAssassinate",
|
||||||
],
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobSpy"
|
||||||
[
|
];
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobAssassinate",
|
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate",
|
const venusNarmerJobs = [
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense",
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobAssassinate",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation"
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate",
|
||||||
],
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense",
|
||||||
[
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation"
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty",
|
];
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty",
|
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty",
|
const microplanetJobs = [
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosGrnSurvivorBounty",
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty",
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosKeyPiecesBounty",
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty",
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosExcavateBounty",
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty",
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty"
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosGrnSurvivorBounty",
|
||||||
],
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosKeyPiecesBounty",
|
||||||
[
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosExcavateBounty",
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty",
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty"
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty",
|
];
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
|
|
||||||
]
|
const microplanetEndlessJobs = [
|
||||||
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty",
|
||||||
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty",
|
||||||
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
|
||||||
];
|
];
|
||||||
|
|
||||||
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
|
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
|
||||||
|
|
||||||
|
const isBeforeNextExpectedWorldStateRefresh = (date: number): boolean => {
|
||||||
|
return Date.now() + 300_000 > date;
|
||||||
|
};
|
||||||
|
|
||||||
const getSortieTime = (day: number): number => {
|
const getSortieTime = (day: number): number => {
|
||||||
const dayStart = EPOCH + day * 86400000;
|
const dayStart = EPOCH + day * 86400000;
|
||||||
const date = new Date(dayStart);
|
const date = new Date(dayStart);
|
||||||
@ -165,6 +171,134 @@ const getSortieTime = (day: number): number => {
|
|||||||
return dayStart + (isDst ? 16 : 17) * 3600000;
|
return dayStart + (isDst ? 16 : 17) * 3600000;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
|
||||||
|
const dayStart = getSortieTime(day);
|
||||||
|
if (!isBeforeNextExpectedWorldStateRefresh(dayStart)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dayEnd = getSortieTime(day + 1);
|
||||||
|
if (Date.now() >= dayEnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rng = new CRng(day);
|
||||||
|
|
||||||
|
const boss = rng.randomElement(sortieBosses);
|
||||||
|
|
||||||
|
const modifiers = [
|
||||||
|
"SORTIE_MODIFIER_LOW_ENERGY",
|
||||||
|
"SORTIE_MODIFIER_IMPACT",
|
||||||
|
"SORTIE_MODIFIER_SLASH",
|
||||||
|
"SORTIE_MODIFIER_PUNCTURE",
|
||||||
|
"SORTIE_MODIFIER_EXIMUS",
|
||||||
|
"SORTIE_MODIFIER_MAGNETIC",
|
||||||
|
"SORTIE_MODIFIER_CORROSIVE",
|
||||||
|
"SORTIE_MODIFIER_VIRAL",
|
||||||
|
"SORTIE_MODIFIER_ELECTRICITY",
|
||||||
|
"SORTIE_MODIFIER_RADIATION",
|
||||||
|
"SORTIE_MODIFIER_GAS",
|
||||||
|
"SORTIE_MODIFIER_FIRE",
|
||||||
|
"SORTIE_MODIFIER_EXPLOSION",
|
||||||
|
"SORTIE_MODIFIER_FREEZE",
|
||||||
|
"SORTIE_MODIFIER_TOXIN",
|
||||||
|
"SORTIE_MODIFIER_POISON",
|
||||||
|
"SORTIE_MODIFIER_HAZARD_RADIATION",
|
||||||
|
"SORTIE_MODIFIER_HAZARD_MAGNETIC",
|
||||||
|
"SORTIE_MODIFIER_HAZARD_FOG", // TODO: push this if the mission tileset is Grineer Forest
|
||||||
|
"SORTIE_MODIFIER_HAZARD_FIRE", // TODO: push this if the mission tileset is Corpus Ship or Grineer Galleon
|
||||||
|
"SORTIE_MODIFIER_HAZARD_ICE",
|
||||||
|
"SORTIE_MODIFIER_HAZARD_COLD",
|
||||||
|
"SORTIE_MODIFIER_SECONDARY_ONLY",
|
||||||
|
"SORTIE_MODIFIER_SHOTGUN_ONLY",
|
||||||
|
"SORTIE_MODIFIER_SNIPER_ONLY",
|
||||||
|
"SORTIE_MODIFIER_RIFLE_ONLY",
|
||||||
|
"SORTIE_MODIFIER_MELEE_ONLY",
|
||||||
|
"SORTIE_MODIFIER_BOW_ONLY"
|
||||||
|
];
|
||||||
|
|
||||||
|
if (sortieBossToFaction[boss] == "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_SHIELDS");
|
||||||
|
if (sortieBossToFaction[boss] != "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_ARMOR");
|
||||||
|
|
||||||
|
const nodes: string[] = [];
|
||||||
|
const availableMissionIndexes: number[] = [];
|
||||||
|
for (const [key, value] of Object.entries(ExportRegions)) {
|
||||||
|
if (
|
||||||
|
sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
|
||||||
|
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
|
||||||
|
value.name.indexOf("Archwing") == -1 &&
|
||||||
|
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
|
||||||
|
value.missionIndex != 5 && // Exclude MT_CAPTURE
|
||||||
|
value.missionIndex != 21 && // Exclude MT_PURIFY
|
||||||
|
value.missionIndex != 23 && // Exclude MT_JUNCTION
|
||||||
|
value.missionIndex <= 28
|
||||||
|
) {
|
||||||
|
if (!availableMissionIndexes.includes(value.missionIndex)) {
|
||||||
|
availableMissionIndexes.push(value.missionIndex);
|
||||||
|
}
|
||||||
|
nodes.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedNodes: { missionType: string; modifierType: string; node: string }[] = [];
|
||||||
|
const missionTypes = new Set();
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const randomIndex = rng.randomInt(0, nodes.length - 1);
|
||||||
|
const node = nodes[randomIndex];
|
||||||
|
let missionIndex = ExportRegions[node].missionIndex;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!["SolNode404", "SolNode411"].includes(node) && // for some reason the game doesn't like missionType changes for these missions
|
||||||
|
missionIndex != 28 &&
|
||||||
|
rng.randomInt(0, 2) == 2
|
||||||
|
) {
|
||||||
|
missionIndex = rng.randomElement(availableMissionIndexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 2 && rng.randomInt(0, 2) == 2) {
|
||||||
|
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
|
||||||
|
const modifierType = rng.randomElement(filteredModifiers);
|
||||||
|
|
||||||
|
if (boss == "SORTIE_BOSS_PHORID") {
|
||||||
|
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node });
|
||||||
|
nodes.splice(randomIndex, 1);
|
||||||
|
continue;
|
||||||
|
} else if (sortieBossNode[boss]) {
|
||||||
|
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node: sortieBossNode[boss] });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const missionType = eMissionType[missionIndex].tag;
|
||||||
|
|
||||||
|
if (missionTypes.has(missionType)) {
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredModifiers =
|
||||||
|
missionType === "MT_TERRITORY"
|
||||||
|
? modifiers.filter(mod => mod != "SORTIE_MODIFIER_HAZARD_RADIATION")
|
||||||
|
: modifiers;
|
||||||
|
|
||||||
|
const modifierType = rng.randomElement(filteredModifiers);
|
||||||
|
|
||||||
|
selectedNodes.push({ missionType, modifierType, node });
|
||||||
|
nodes.splice(randomIndex, 1);
|
||||||
|
missionTypes.add(missionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" },
|
||||||
|
Activation: { $date: { $numberLong: dayStart.toString() } },
|
||||||
|
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
||||||
|
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
|
||||||
|
Seed: day,
|
||||||
|
Boss: boss,
|
||||||
|
Variants: selectedNodes
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
|
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
|
||||||
x.startsWith("/Lotus/Types/Challenges/Seasons/Daily/")
|
x.startsWith("/Lotus/Types/Challenges/Seasons/Daily/")
|
||||||
);
|
);
|
||||||
@ -228,9 +362,10 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel,
|
BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel,
|
||||||
Time: config.worldState?.lockTime || Math.round(Date.now() / 1000),
|
Time: config.worldState?.lockTime || Math.round(Date.now() / 1000),
|
||||||
Goals: [],
|
Goals: [],
|
||||||
GlobalUpgrades: [],
|
Alerts: [],
|
||||||
Sorties: [],
|
Sorties: [],
|
||||||
LiteSorties: [],
|
LiteSorties: [],
|
||||||
|
GlobalUpgrades: [],
|
||||||
EndlessXpChoices: [],
|
EndlessXpChoices: [],
|
||||||
SeasonInfo: {
|
SeasonInfo: {
|
||||||
Activation: { $date: { $numberLong: "1715796000000" } },
|
Activation: { $date: { $numberLong: "1715796000000" } },
|
||||||
@ -239,36 +374,10 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
Season: 14,
|
Season: 14,
|
||||||
Phase: 0,
|
Phase: 0,
|
||||||
Params: "",
|
Params: "",
|
||||||
ActiveChallenges: [
|
ActiveChallenges: []
|
||||||
getSeasonDailyChallenge(day - 2),
|
|
||||||
getSeasonDailyChallenge(day - 1),
|
|
||||||
getSeasonDailyChallenge(day - 0),
|
|
||||||
getSeasonWeeklyChallenge(week, 0),
|
|
||||||
getSeasonWeeklyChallenge(week, 1),
|
|
||||||
getSeasonWeeklyHardChallenge(week, 2),
|
|
||||||
getSeasonWeeklyHardChallenge(week, 3),
|
|
||||||
{
|
|
||||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
|
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
|
||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
|
||||||
Challenge:
|
|
||||||
"/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" + (week - 12)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
|
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
|
||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
|
||||||
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" + (week - 12)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
|
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
|
||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
|
||||||
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" + (week - 12)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
...staticWorldState
|
...staticWorldState,
|
||||||
|
SyndicateMissions: [...staticWorldState.SyndicateMissions]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config.worldState?.starDays) {
|
if (config.worldState?.starDays) {
|
||||||
@ -288,73 +397,307 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nightwave Challenges
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 2));
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 1));
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 0));
|
||||||
|
if (isBeforeNextExpectedWorldStateRefresh(EPOCH + (day + 1) * 86400000)) {
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day + 1));
|
||||||
|
}
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 0));
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 1));
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 2));
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 3));
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push({
|
||||||
|
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
|
||||||
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||||
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||||
|
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" + (week - 12)
|
||||||
|
});
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push({
|
||||||
|
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
|
||||||
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||||
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||||
|
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" + (week - 12)
|
||||||
|
});
|
||||||
|
worldState.SeasonInfo.ActiveChallenges.push({
|
||||||
|
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
|
||||||
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||||
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||||
|
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" + (week - 12)
|
||||||
|
});
|
||||||
|
// TODO: Provide upcoming weekly acts if rollover is imminent
|
||||||
|
|
||||||
// Elite Sanctuary Onslaught cycling every week
|
// Elite Sanctuary Onslaught cycling every week
|
||||||
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful
|
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful
|
||||||
|
|
||||||
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
|
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
|
||||||
const bountyCycle = Math.trunc(Date.now() / 9000000);
|
let bountyCycle = Math.trunc(Date.now() / 9000000);
|
||||||
const bountyCycleStart = bountyCycle * 9000000;
|
let bountyCycleEnd: number | undefined;
|
||||||
const bountyCycleEnd = bountyCycleStart + 9000000;
|
do {
|
||||||
worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "ZarimanSyndicate")] = {
|
const bountyCycleStart = bountyCycle * 9000000;
|
||||||
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000029" },
|
bountyCycleEnd = bountyCycleStart + 9000000;
|
||||||
Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
|
worldState.SyndicateMissions.push({
|
||||||
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
|
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000029" },
|
||||||
Tag: "ZarimanSyndicate",
|
Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
|
||||||
Seed: bountyCycle,
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
|
||||||
Nodes: []
|
Tag: "ZarimanSyndicate",
|
||||||
};
|
Seed: bountyCycle,
|
||||||
worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "EntratiLabSyndicate")] = {
|
Nodes: []
|
||||||
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000004" },
|
});
|
||||||
Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
|
worldState.SyndicateMissions.push({
|
||||||
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
|
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000004" },
|
||||||
Tag: "EntratiLabSyndicate",
|
Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
|
||||||
Seed: bountyCycle,
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
|
||||||
Nodes: []
|
Tag: "EntratiLabSyndicate",
|
||||||
};
|
Seed: bountyCycle,
|
||||||
worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "HexSyndicate")] = {
|
Nodes: []
|
||||||
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000006" },
|
});
|
||||||
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
worldState.SyndicateMissions.push({
|
||||||
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000006" },
|
||||||
Tag: "HexSyndicate",
|
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
||||||
Seed: bountyCycle,
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
||||||
Nodes: []
|
Tag: "HexSyndicate",
|
||||||
};
|
Seed: bountyCycle,
|
||||||
for (const syndicateInfo of worldState.SyndicateMissions) {
|
Nodes: []
|
||||||
if (syndicateInfo.Jobs && syndicateInfo.Seed != bountyCycle) {
|
});
|
||||||
syndicateInfo.Activation.$date.$numberLong = bountyCycleStart.toString(10);
|
|
||||||
syndicateInfo.Expiry.$date.$numberLong = bountyCycleEnd.toString(10);
|
const table = String.fromCharCode(65 + (bountyCycle % 3));
|
||||||
syndicateInfo.Seed = bountyCycle;
|
const vaultTable = String.fromCharCode(65 + ((bountyCycle + 1) % 3));
|
||||||
logger.debug(`refreshing jobs for ${syndicateInfo.Tag}`);
|
const deimosDTable = String.fromCharCode(65 + (bountyCycle % 2));
|
||||||
|
|
||||||
|
// TODO: xpAmounts need to be calculated based on the jobType somehow?
|
||||||
|
|
||||||
|
{
|
||||||
const rng = new CRng(bountyCycle);
|
const rng = new CRng(bountyCycle);
|
||||||
const table = String.fromCharCode(65 + (bountyCycle % 3));
|
worldState.SyndicateMissions.push({
|
||||||
const vaultTable = String.fromCharCode(65 + ((bountyCycle + 1) % 3));
|
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000008" },
|
||||||
const deimosDTable = String.fromCharCode(65 + (bountyCycle % 2));
|
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
||||||
//console.log({ bountyCycleStart, bountyCycleEnd, table, vaultTable, deimosDTable });
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
||||||
for (const jobInfo of syndicateInfo.Jobs) {
|
Tag: "CetusSyndicate",
|
||||||
if (jobInfo.jobType) {
|
Seed: bountyCycle,
|
||||||
let found = false;
|
Nodes: [],
|
||||||
for (const jobSet of jobSets) {
|
Jobs: [
|
||||||
if (jobSet.indexOf(jobInfo.jobType) != -1) {
|
{
|
||||||
jobInfo.jobType = rng.randomElement(jobSet);
|
jobType: rng.randomElement(eidolonJobs),
|
||||||
// TODO: xpAmounts seems like it might need to differ depending on the job?
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${table}Rewards`,
|
||||||
found = true;
|
masteryReq: 0,
|
||||||
break;
|
minEnemyLevel: 5,
|
||||||
}
|
maxEnemyLevel: 15,
|
||||||
|
xpAmounts: [430, 430, 430]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(eidolonJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${table}Rewards`,
|
||||||
|
masteryReq: 1,
|
||||||
|
minEnemyLevel: 10,
|
||||||
|
maxEnemyLevel: 30,
|
||||||
|
xpAmounts: [620, 620, 620]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(eidolonJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${table}Rewards`,
|
||||||
|
masteryReq: 2,
|
||||||
|
minEnemyLevel: 20,
|
||||||
|
maxEnemyLevel: 40,
|
||||||
|
xpAmounts: [670, 670, 670, 990]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(eidolonJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${table}Rewards`,
|
||||||
|
masteryReq: 3,
|
||||||
|
minEnemyLevel: 30,
|
||||||
|
maxEnemyLevel: 50,
|
||||||
|
xpAmounts: [570, 570, 570, 570, 1110]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(eidolonJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
|
||||||
|
masteryReq: 5,
|
||||||
|
minEnemyLevel: 40,
|
||||||
|
maxEnemyLevel: 60,
|
||||||
|
xpAmounts: [740, 740, 740, 740, 1450]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(eidolonJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
|
||||||
|
masteryReq: 10,
|
||||||
|
minEnemyLevel: 100,
|
||||||
|
maxEnemyLevel: 100,
|
||||||
|
xpAmounts: [840, 840, 840, 840, 1660]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(eidolonNarmerJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTable${table}Rewards`,
|
||||||
|
masteryReq: 0,
|
||||||
|
minEnemyLevel: 50,
|
||||||
|
maxEnemyLevel: 70,
|
||||||
|
xpAmounts: [840, 840, 840, 840, 1650]
|
||||||
}
|
}
|
||||||
if (!found) {
|
]
|
||||||
logger.warn(`no job set found for type ${jobInfo.jobType}`);
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
if (jobInfo.endless || jobInfo.isVault) {
|
|
||||||
jobInfo.rewards = jobInfo.rewards.replace(/Table.Rewards/, `Table${vaultTable}Rewards`);
|
|
||||||
} else if (jobInfo.rewards.startsWith("/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierD")) {
|
|
||||||
jobInfo.rewards = jobInfo.rewards.replace(/Table.Rewards/, `Table${deimosDTable}Rewards`);
|
|
||||||
} else if (!jobInfo.rewards.startsWith("/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierE")) {
|
|
||||||
jobInfo.rewards = jobInfo.rewards.replace(/Table.Rewards/, `Table${table}Rewards`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
{
|
||||||
|
const rng = new CRng(bountyCycle);
|
||||||
|
worldState.SyndicateMissions.push({
|
||||||
|
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000025" },
|
||||||
|
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
||||||
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
||||||
|
Tag: "SolarisSyndicate",
|
||||||
|
Seed: bountyCycle,
|
||||||
|
Nodes: [],
|
||||||
|
Jobs: [
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(venusJobs),
|
||||||
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`,
|
||||||
|
masteryReq: 0,
|
||||||
|
minEnemyLevel: 5,
|
||||||
|
maxEnemyLevel: 15,
|
||||||
|
xpAmounts: [340, 340, 340]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(venusJobs),
|
||||||
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`,
|
||||||
|
masteryReq: 1,
|
||||||
|
minEnemyLevel: 10,
|
||||||
|
maxEnemyLevel: 30,
|
||||||
|
xpAmounts: [660, 660, 660]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(venusJobs),
|
||||||
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`,
|
||||||
|
masteryReq: 2,
|
||||||
|
minEnemyLevel: 20,
|
||||||
|
maxEnemyLevel: 40,
|
||||||
|
xpAmounts: [610, 610, 610, 900]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(venusJobs),
|
||||||
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`,
|
||||||
|
masteryReq: 3,
|
||||||
|
minEnemyLevel: 30,
|
||||||
|
maxEnemyLevel: 50,
|
||||||
|
xpAmounts: [600, 600, 600, 600, 1170]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(venusJobs),
|
||||||
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
|
||||||
|
masteryReq: 5,
|
||||||
|
minEnemyLevel: 40,
|
||||||
|
maxEnemyLevel: 60,
|
||||||
|
xpAmounts: [690, 690, 690, 690, 1350]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(venusJobs),
|
||||||
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
|
||||||
|
masteryReq: 10,
|
||||||
|
minEnemyLevel: 100,
|
||||||
|
maxEnemyLevel: 100,
|
||||||
|
xpAmounts: [840, 840, 840, 840, 1660]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(venusNarmerJobs),
|
||||||
|
rewards: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTableBRewards",
|
||||||
|
masteryReq: 0,
|
||||||
|
minEnemyLevel: 50,
|
||||||
|
maxEnemyLevel: 70,
|
||||||
|
xpAmounts: [780, 780, 780, 780, 1540]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const rng = new CRng(bountyCycle);
|
||||||
|
worldState.SyndicateMissions.push({
|
||||||
|
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000002" },
|
||||||
|
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
||||||
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
||||||
|
Tag: "EntratiSyndicate",
|
||||||
|
Seed: bountyCycle,
|
||||||
|
Nodes: [],
|
||||||
|
Jobs: [
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(microplanetJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${table}Rewards`,
|
||||||
|
masteryReq: 0,
|
||||||
|
minEnemyLevel: 5,
|
||||||
|
maxEnemyLevel: 15,
|
||||||
|
xpAmounts: [5, 5, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(microplanetJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${table}Rewards`,
|
||||||
|
masteryReq: 1,
|
||||||
|
minEnemyLevel: 15,
|
||||||
|
maxEnemyLevel: 25,
|
||||||
|
xpAmounts: [12, 12, 12]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(microplanetEndlessJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierBTable${table}Rewards`,
|
||||||
|
masteryReq: 5,
|
||||||
|
minEnemyLevel: 25,
|
||||||
|
maxEnemyLevel: 30,
|
||||||
|
endless: true,
|
||||||
|
xpAmounts: [14, 14, 14]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(microplanetJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${deimosDTable}Rewards`,
|
||||||
|
masteryReq: 2,
|
||||||
|
minEnemyLevel: 30,
|
||||||
|
maxEnemyLevel: 40,
|
||||||
|
xpAmounts: [17, 17, 17, 25]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(microplanetJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
|
||||||
|
masteryReq: 3,
|
||||||
|
minEnemyLevel: 40,
|
||||||
|
maxEnemyLevel: 60,
|
||||||
|
xpAmounts: [22, 22, 22, 22, 43]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobType: rng.randomElement(microplanetJobs),
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
|
||||||
|
masteryReq: 10,
|
||||||
|
minEnemyLevel: 100,
|
||||||
|
maxEnemyLevel: 100,
|
||||||
|
xpAmounts: [25, 25, 25, 25, 50]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierATable${vaultTable}Rewards`,
|
||||||
|
masteryReq: 5,
|
||||||
|
minEnemyLevel: 30,
|
||||||
|
maxEnemyLevel: 40,
|
||||||
|
xpAmounts: [2, 2, 2, 4],
|
||||||
|
locationTag: "ChamberB",
|
||||||
|
isVault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierBTable${vaultTable}Rewards`,
|
||||||
|
masteryReq: 5,
|
||||||
|
minEnemyLevel: 40,
|
||||||
|
maxEnemyLevel: 50,
|
||||||
|
xpAmounts: [4, 4, 4, 5],
|
||||||
|
locationTag: "ChamberA",
|
||||||
|
isVault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierCTable${vaultTable}Rewards`,
|
||||||
|
masteryReq: 5,
|
||||||
|
minEnemyLevel: 50,
|
||||||
|
maxEnemyLevel: 60,
|
||||||
|
xpAmounts: [5, 5, 5, 7],
|
||||||
|
locationTag: "ChamberC",
|
||||||
|
isVault: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} while (isBeforeNextExpectedWorldStateRefresh(bountyCycleEnd) && ++bountyCycle);
|
||||||
|
|
||||||
if (config.worldState?.creditBoost) {
|
if (config.worldState?.creditBoost) {
|
||||||
worldState.GlobalUpgrades.push({
|
worldState.GlobalUpgrades.push({
|
||||||
@ -394,142 +737,11 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sortie cycling every day
|
// Sortie cycling every day
|
||||||
{
|
pushSortieIfRelevant(worldState.Sorties, day - 1);
|
||||||
let genDay;
|
pushSortieIfRelevant(worldState.Sorties, day);
|
||||||
let dayStart;
|
|
||||||
let dayEnd;
|
|
||||||
const sortieRolloverToday = getSortieTime(day);
|
|
||||||
if (Date.now() < sortieRolloverToday) {
|
|
||||||
// Early in the day, generate sortie for `day - 1`, expiring at `sortieRolloverToday`.
|
|
||||||
genDay = day - 1;
|
|
||||||
dayStart = getSortieTime(genDay);
|
|
||||||
dayEnd = sortieRolloverToday;
|
|
||||||
} else {
|
|
||||||
// Late in the day, generate sortie for `day`, expiring at `getSortieTime(day + 1)`.
|
|
||||||
genDay = day;
|
|
||||||
dayStart = sortieRolloverToday;
|
|
||||||
dayEnd = getSortieTime(day + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rng = new CRng(genDay);
|
|
||||||
|
|
||||||
const boss = rng.randomElement(sortieBosses);
|
|
||||||
|
|
||||||
const modifiers = [
|
|
||||||
"SORTIE_MODIFIER_LOW_ENERGY",
|
|
||||||
"SORTIE_MODIFIER_IMPACT",
|
|
||||||
"SORTIE_MODIFIER_SLASH",
|
|
||||||
"SORTIE_MODIFIER_PUNCTURE",
|
|
||||||
"SORTIE_MODIFIER_EXIMUS",
|
|
||||||
"SORTIE_MODIFIER_MAGNETIC",
|
|
||||||
"SORTIE_MODIFIER_CORROSIVE",
|
|
||||||
"SORTIE_MODIFIER_VIRAL",
|
|
||||||
"SORTIE_MODIFIER_ELECTRICITY",
|
|
||||||
"SORTIE_MODIFIER_RADIATION",
|
|
||||||
"SORTIE_MODIFIER_GAS",
|
|
||||||
"SORTIE_MODIFIER_FIRE",
|
|
||||||
"SORTIE_MODIFIER_EXPLOSION",
|
|
||||||
"SORTIE_MODIFIER_FREEZE",
|
|
||||||
"SORTIE_MODIFIER_TOXIN",
|
|
||||||
"SORTIE_MODIFIER_POISON",
|
|
||||||
"SORTIE_MODIFIER_HAZARD_RADIATION",
|
|
||||||
"SORTIE_MODIFIER_HAZARD_MAGNETIC",
|
|
||||||
"SORTIE_MODIFIER_HAZARD_FOG", // TODO: push this if the mission tileset is Grineer Forest
|
|
||||||
"SORTIE_MODIFIER_HAZARD_FIRE", // TODO: push this if the mission tileset is Corpus Ship or Grineer Galleon
|
|
||||||
"SORTIE_MODIFIER_HAZARD_ICE",
|
|
||||||
"SORTIE_MODIFIER_HAZARD_COLD",
|
|
||||||
"SORTIE_MODIFIER_SECONDARY_ONLY",
|
|
||||||
"SORTIE_MODIFIER_SHOTGUN_ONLY",
|
|
||||||
"SORTIE_MODIFIER_SNIPER_ONLY",
|
|
||||||
"SORTIE_MODIFIER_RIFLE_ONLY",
|
|
||||||
"SORTIE_MODIFIER_MELEE_ONLY",
|
|
||||||
"SORTIE_MODIFIER_BOW_ONLY"
|
|
||||||
];
|
|
||||||
|
|
||||||
if (sortieBossToFaction[boss] == "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_SHIELDS");
|
|
||||||
if (sortieBossToFaction[boss] != "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_ARMOR");
|
|
||||||
|
|
||||||
const nodes: string[] = [];
|
|
||||||
const availableMissionIndexes: number[] = [];
|
|
||||||
for (const [key, value] of Object.entries(ExportRegions)) {
|
|
||||||
if (
|
|
||||||
sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
|
|
||||||
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
|
|
||||||
value.name.indexOf("Archwing") == -1 &&
|
|
||||||
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
|
|
||||||
value.missionIndex != 5 && // Exclude MT_CAPTURE
|
|
||||||
value.missionIndex != 21 && // Exclude MT_PURIFY
|
|
||||||
value.missionIndex != 23 && // Exclude MT_JUNCTION
|
|
||||||
value.missionIndex <= 28
|
|
||||||
) {
|
|
||||||
if (!availableMissionIndexes.includes(value.missionIndex)) {
|
|
||||||
availableMissionIndexes.push(value.missionIndex);
|
|
||||||
}
|
|
||||||
nodes.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedNodes: { missionType: string; modifierType: string; node: string }[] = [];
|
|
||||||
const missionTypes = new Set();
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
const randomIndex = rng.randomInt(0, nodes.length - 1);
|
|
||||||
const node = nodes[randomIndex];
|
|
||||||
let missionIndex = ExportRegions[node].missionIndex;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!["SolNode404", "SolNode411"].includes(node) && // for some reason the game doesn't like missionType changes for these missions
|
|
||||||
missionIndex != 28 &&
|
|
||||||
rng.randomInt(0, 2) == 2
|
|
||||||
) {
|
|
||||||
missionIndex = rng.randomElement(availableMissionIndexes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == 2 && rng.randomInt(0, 2) == 2) {
|
|
||||||
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
|
|
||||||
const modifierType = rng.randomElement(filteredModifiers);
|
|
||||||
|
|
||||||
if (boss == "SORTIE_BOSS_PHORID") {
|
|
||||||
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node });
|
|
||||||
nodes.splice(randomIndex, 1);
|
|
||||||
continue;
|
|
||||||
} else if (sortieBossNode[boss]) {
|
|
||||||
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node: sortieBossNode[boss] });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const missionType = eMissionType[missionIndex].tag;
|
|
||||||
|
|
||||||
if (missionTypes.has(missionType)) {
|
|
||||||
i--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredModifiers =
|
|
||||||
missionType === "MT_TERRITORY"
|
|
||||||
? modifiers.filter(mod => mod != "SORTIE_MODIFIER_HAZARD_RADIATION")
|
|
||||||
: modifiers;
|
|
||||||
|
|
||||||
const modifierType = rng.randomElement(filteredModifiers);
|
|
||||||
|
|
||||||
selectedNodes.push({ missionType, modifierType, node });
|
|
||||||
nodes.splice(randomIndex, 1);
|
|
||||||
missionTypes.add(missionType);
|
|
||||||
}
|
|
||||||
|
|
||||||
worldState.Sorties.push({
|
|
||||||
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" },
|
|
||||||
Activation: { $date: { $numberLong: dayStart.toString() } },
|
|
||||||
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
|
||||||
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
|
|
||||||
Seed: genDay,
|
|
||||||
Boss: boss,
|
|
||||||
Variants: selectedNodes
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Archon Hunt cycling every week
|
// Archon Hunt cycling every week
|
||||||
|
// TODO: Handle imminent rollover
|
||||||
{
|
{
|
||||||
const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3];
|
const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3];
|
||||||
const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3];
|
const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3];
|
||||||
@ -622,6 +834,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 1999 Calendar Season cycling every week + YearIteration every 4 weeks
|
// 1999 Calendar Season cycling every week + YearIteration every 4 weeks
|
||||||
|
// TODO: Handle imminent rollover
|
||||||
worldState.KnownCalendarSeasons[0].Activation = { $date: { $numberLong: weekStart.toString() } };
|
worldState.KnownCalendarSeasons[0].Activation = { $date: { $numberLong: weekStart.toString() } };
|
||||||
worldState.KnownCalendarSeasons[0].Expiry = { $date: { $numberLong: weekEnd.toString() } };
|
worldState.KnownCalendarSeasons[0].Expiry = { $date: { $numberLong: weekEnd.toString() } };
|
||||||
worldState.KnownCalendarSeasons[0].Season = ["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"][week % 4];
|
worldState.KnownCalendarSeasons[0].Season = ["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"][week % 4];
|
||||||
|
@ -194,7 +194,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
Mailbox?: IMailboxClient;
|
Mailbox?: IMailboxClient;
|
||||||
SubscribedToEmails: number;
|
SubscribedToEmails: number;
|
||||||
Created: IMongoDate;
|
Created: IMongoDate;
|
||||||
RewardSeed: number;
|
RewardSeed: number | bigint;
|
||||||
RegularCredits: number;
|
RegularCredits: number;
|
||||||
PremiumCredits: number;
|
PremiumCredits: number;
|
||||||
PremiumCreditsFree: number;
|
PremiumCreditsFree: number;
|
||||||
@ -947,15 +947,17 @@ export interface IPersonalTechProjectDatabase {
|
|||||||
State: number;
|
State: number;
|
||||||
ReqCredits: number;
|
ReqCredits: number;
|
||||||
ItemType: string;
|
ItemType: string;
|
||||||
|
ProductCategory?: string;
|
||||||
|
CategoryItemId?: Types.ObjectId;
|
||||||
ReqItems: ITypeCount[];
|
ReqItems: ITypeCount[];
|
||||||
HasContributions?: boolean;
|
HasContributions?: boolean;
|
||||||
CompletionDate?: Date;
|
CompletionDate?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPersonalTechProjectClient extends Omit<IPersonalTechProjectDatabase, "CompletionDate"> {
|
export interface IPersonalTechProjectClient
|
||||||
CompletionDate?: IMongoDate;
|
extends Omit<IPersonalTechProjectDatabase, "CategoryItemId" | "CompletionDate"> {
|
||||||
ProductCategory?: string;
|
|
||||||
CategoryItemId?: IOid;
|
CategoryItemId?: IOid;
|
||||||
|
CompletionDate?: IMongoDate;
|
||||||
ItemId: IOid;
|
ItemId: IOid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ export type IInventoryChanges = {
|
|||||||
Drones?: IDroneClient[];
|
Drones?: IDroneClient[];
|
||||||
MiscItems?: IMiscItem[];
|
MiscItems?: IMiscItem[];
|
||||||
EmailItems?: ITypeCount[];
|
EmailItems?: ITypeCount[];
|
||||||
|
CrewShipRawSalvage?: ITypeCount[];
|
||||||
Nemesis?: Partial<INemesisClient>;
|
Nemesis?: Partial<INemesisClient>;
|
||||||
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
|
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
|
||||||
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
|
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
|
||||||
|
@ -141,7 +141,7 @@ export interface IRewardInfo {
|
|||||||
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;
|
||||||
rewardSeed?: number;
|
rewardSeed?: number | bigint;
|
||||||
periodicMissionTag?: string;
|
periodicMissionTag?: string;
|
||||||
|
|
||||||
// for bounties, only EOM_AFK and node are given from above, plus:
|
// for bounties, only EOM_AFK and node are given from above, plus:
|
||||||
|
@ -5,10 +5,11 @@ export interface IWorldState {
|
|||||||
BuildLabel: string;
|
BuildLabel: string;
|
||||||
Time: number;
|
Time: number;
|
||||||
Goals: IGoal[];
|
Goals: IGoal[];
|
||||||
SyndicateMissions: ISyndicateMissionInfo[];
|
Alerts: [];
|
||||||
GlobalUpgrades: IGlobalUpgrade[];
|
|
||||||
Sorties: ISortie[];
|
Sorties: ISortie[];
|
||||||
LiteSorties: ILiteSortie[];
|
LiteSorties: ILiteSortie[];
|
||||||
|
SyndicateMissions: ISyndicateMissionInfo[];
|
||||||
|
GlobalUpgrades: IGlobalUpgrade[];
|
||||||
NodeOverrides: INodeOverride[];
|
NodeOverrides: INodeOverride[];
|
||||||
EndlessXpChoices: IEndlessXpChoice[];
|
EndlessXpChoices: IEndlessXpChoice[];
|
||||||
SeasonInfo: {
|
SeasonInfo: {
|
||||||
|
@ -278,248 +278,6 @@
|
|||||||
"Tag": "SteelMeridianSyndicate",
|
"Tag": "SteelMeridianSyndicate",
|
||||||
"Seed": 42366,
|
"Seed": 42366,
|
||||||
"Nodes": ["SolNode27", "SolNode107", "SolNode214", "SettlementNode1", "SolNode177", "SolNode141", "SolNode408"]
|
"Nodes": ["SolNode27", "SolNode107", "SolNode214", "SettlementNode1", "SolNode177", "SolNode141", "SolNode408"]
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": { "$oid": "663a71c80000000000000002" },
|
|
||||||
"Activation": { "$date": { "$numberLong": "1715106248403" } },
|
|
||||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
|
||||||
"Tag": "EntratiSyndicate",
|
|
||||||
"Seed": 99561,
|
|
||||||
"Nodes": [],
|
|
||||||
"Jobs": [
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosGrnSurvivorBounty",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATableBRewards",
|
|
||||||
"masteryReq": 0,
|
|
||||||
"minEnemyLevel": 5,
|
|
||||||
"maxEnemyLevel": 15,
|
|
||||||
"xpAmounts": [5, 5, 5]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTableBRewards",
|
|
||||||
"masteryReq": 1,
|
|
||||||
"minEnemyLevel": 15,
|
|
||||||
"maxEnemyLevel": 25,
|
|
||||||
"xpAmounts": [12, 12, 12]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierBTableARewards",
|
|
||||||
"masteryReq": 5,
|
|
||||||
"minEnemyLevel": 25,
|
|
||||||
"maxEnemyLevel": 30,
|
|
||||||
"endless": true,
|
|
||||||
"xpAmounts": [14, 14, 14]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTableBRewards",
|
|
||||||
"masteryReq": 2,
|
|
||||||
"minEnemyLevel": 30,
|
|
||||||
"maxEnemyLevel": 40,
|
|
||||||
"xpAmounts": [17, 17, 17, 25]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosKeyPiecesBounty",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards",
|
|
||||||
"masteryReq": 3,
|
|
||||||
"minEnemyLevel": 40,
|
|
||||||
"maxEnemyLevel": 60,
|
|
||||||
"xpAmounts": [22, 22, 22, 22, 43]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosExcavateBounty",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards",
|
|
||||||
"masteryReq": 10,
|
|
||||||
"minEnemyLevel": 100,
|
|
||||||
"maxEnemyLevel": 100,
|
|
||||||
"xpAmounts": [25, 25, 25, 25, 50]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierATableCRewards",
|
|
||||||
"masteryReq": 5,
|
|
||||||
"minEnemyLevel": 30,
|
|
||||||
"maxEnemyLevel": 40,
|
|
||||||
"xpAmounts": [2, 2, 2, 4],
|
|
||||||
"locationTag": "ChamberB",
|
|
||||||
"isVault": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierBTableCRewards",
|
|
||||||
"masteryReq": 5,
|
|
||||||
"minEnemyLevel": 40,
|
|
||||||
"maxEnemyLevel": 50,
|
|
||||||
"xpAmounts": [4, 4, 4, 5],
|
|
||||||
"locationTag": "ChamberA",
|
|
||||||
"isVault": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierCTableCRewards",
|
|
||||||
"masteryReq": 5,
|
|
||||||
"minEnemyLevel": 50,
|
|
||||||
"maxEnemyLevel": 60,
|
|
||||||
"xpAmounts": [5, 5, 5, 7],
|
|
||||||
"locationTag": "ChamberC",
|
|
||||||
"isVault": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": { "$oid": "663a71c80000000000000004" },
|
|
||||||
"Activation": { "$date": { "$numberLong": "1715106248403" } },
|
|
||||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
|
||||||
"Tag": "EntratiLabSyndicate",
|
|
||||||
"Seed": 99562,
|
|
||||||
"Nodes": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": { "$oid": "663a71c80000000000000008" },
|
|
||||||
"Activation": { "$date": { "$numberLong": "1715106248403" } },
|
|
||||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
|
||||||
"Tag": "CetusSyndicate",
|
|
||||||
"Seed": 99561,
|
|
||||||
"Nodes": [],
|
|
||||||
"Jobs": [
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATableBRewards",
|
|
||||||
"masteryReq": 0,
|
|
||||||
"minEnemyLevel": 5,
|
|
||||||
"maxEnemyLevel": 15,
|
|
||||||
"xpAmounts": [430, 430, 430]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyLib",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTableBRewards",
|
|
||||||
"masteryReq": 1,
|
|
||||||
"minEnemyLevel": 10,
|
|
||||||
"maxEnemyLevel": 30,
|
|
||||||
"xpAmounts": [620, 620, 620]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTableBRewards",
|
|
||||||
"masteryReq": 2,
|
|
||||||
"minEnemyLevel": 20,
|
|
||||||
"maxEnemyLevel": 40,
|
|
||||||
"xpAmounts": [670, 670, 670, 990]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Eidolon/Jobs/CaptureBountyCapTwo",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTableBRewards",
|
|
||||||
"masteryReq": 3,
|
|
||||||
"minEnemyLevel": 30,
|
|
||||||
"maxEnemyLevel": 50,
|
|
||||||
"xpAmounts": [570, 570, 570, 570, 1110]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyCache",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETableBRewards",
|
|
||||||
"masteryReq": 5,
|
|
||||||
"minEnemyLevel": 40,
|
|
||||||
"maxEnemyLevel": 60,
|
|
||||||
"xpAmounts": [740, 740, 740, 740, 1450]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyCap",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETableBRewards",
|
|
||||||
"masteryReq": 10,
|
|
||||||
"minEnemyLevel": 100,
|
|
||||||
"maxEnemyLevel": 100,
|
|
||||||
"xpAmounts": [840, 840, 840, 840, 1660]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AssassinateBountyAss",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTableBRewards",
|
|
||||||
"masteryReq": 0,
|
|
||||||
"minEnemyLevel": 50,
|
|
||||||
"maxEnemyLevel": 70,
|
|
||||||
"xpAmounts": [840, 840, 840, 840, 1650]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": { "$oid": "663a71c80000000000000025" },
|
|
||||||
"Activation": { "$date": { "$numberLong": "1715106248403" } },
|
|
||||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
|
||||||
"Tag": "SolarisSyndicate",
|
|
||||||
"Seed": 99561,
|
|
||||||
"Nodes": [],
|
|
||||||
"Jobs": [
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobSpy",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATableBRewards",
|
|
||||||
"masteryReq": 0,
|
|
||||||
"minEnemyLevel": 5,
|
|
||||||
"maxEnemyLevel": 15,
|
|
||||||
"xpAmounts": [340, 340, 340]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobResource",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTableBRewards",
|
|
||||||
"masteryReq": 1,
|
|
||||||
"minEnemyLevel": 10,
|
|
||||||
"maxEnemyLevel": 30,
|
|
||||||
"xpAmounts": [660, 660, 660]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobRecovery",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTableBRewards",
|
|
||||||
"masteryReq": 2,
|
|
||||||
"minEnemyLevel": 20,
|
|
||||||
"maxEnemyLevel": 40,
|
|
||||||
"xpAmounts": [610, 610, 610, 900]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobCaches",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTableBRewards",
|
|
||||||
"masteryReq": 3,
|
|
||||||
"minEnemyLevel": 30,
|
|
||||||
"maxEnemyLevel": 50,
|
|
||||||
"xpAmounts": [600, 600, 600, 600, 1170]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETableBRewards",
|
|
||||||
"masteryReq": 5,
|
|
||||||
"minEnemyLevel": 40,
|
|
||||||
"maxEnemyLevel": 60,
|
|
||||||
"xpAmounts": [690, 690, 690, 690, 1350]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Venus/Jobs/VenusChaosJobExcavation",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETableBRewards",
|
|
||||||
"masteryReq": 10,
|
|
||||||
"minEnemyLevel": 100,
|
|
||||||
"maxEnemyLevel": 100,
|
|
||||||
"xpAmounts": [840, 840, 840, 840, 1660]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"jobType": "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate",
|
|
||||||
"rewards": "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTableBRewards",
|
|
||||||
"masteryReq": 0,
|
|
||||||
"minEnemyLevel": 50,
|
|
||||||
"maxEnemyLevel": 70,
|
|
||||||
"xpAmounts": [780, 780, 780, 780, 1540]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": { "$oid": "663a71c80000000000000029" },
|
|
||||||
"Activation": { "$date": { "$numberLong": "1715106248403" } },
|
|
||||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
|
||||||
"Tag": "ZarimanSyndicate",
|
|
||||||
"Seed": 99562,
|
|
||||||
"Nodes": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": { "$oid": "676b8d340000000000000006" },
|
|
||||||
"Activation": { "$date": { "$numberLong": "1735101748215" } },
|
|
||||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
|
||||||
"Tag": "HexSyndicate",
|
|
||||||
"Seed": 33872,
|
|
||||||
"Nodes": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ActiveMissions": [
|
"ActiveMissions": [
|
||||||
|
@ -595,6 +595,10 @@
|
|||||||
<input class="form-check-input" type="checkbox" id="noDailyStandingLimits" />
|
<input class="form-check-input" type="checkbox" id="noDailyStandingLimits" />
|
||||||
<label class="form-check-label" for="noDailyStandingLimits" data-loc="cheats_noDailyStandingLimits"></label>
|
<label class="form-check-label" for="noDailyStandingLimits" data-loc="cheats_noDailyStandingLimits"></label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="noDailyFocusLimit" />
|
||||||
|
<label class="form-check-label" for="noDailyFocusLimit" data-loc="cheats_noDailyFocusLimit"></label>
|
||||||
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="noArgonCrystalDecay" />
|
<input class="form-check-input" type="checkbox" id="noArgonCrystalDecay" />
|
||||||
<label class="form-check-label" for="noArgonCrystalDecay" data-loc="cheats_noArgonCrystalDecay"></label>
|
<label class="form-check-label" for="noArgonCrystalDecay" data-loc="cheats_noArgonCrystalDecay"></label>
|
||||||
|
@ -133,13 +133,14 @@ dict = {
|
|||||||
cheats_unlockDoubleCapacityPotatoesEverywhere: `Orokin Reaktor & Beschleuniger überall`,
|
cheats_unlockDoubleCapacityPotatoesEverywhere: `Orokin Reaktor & Beschleuniger überall`,
|
||||||
cheats_unlockExilusEverywhere: `Exilus-Adapter überall`,
|
cheats_unlockExilusEverywhere: `Exilus-Adapter überall`,
|
||||||
cheats_unlockArcanesEverywhere: `Arkana-Adapter überall`,
|
cheats_unlockArcanesEverywhere: `Arkana-Adapter überall`,
|
||||||
cheats_noDailyStandingLimits: `Kein tägliches Ansehenslimit`,
|
cheats_noDailyStandingLimits: `Kein tägliches Ansehen Limit`,
|
||||||
|
cheats_noDailyFocusLimit: `Kein tägliches Fokus-Limit`,
|
||||||
cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`,
|
cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`,
|
||||||
cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`,
|
cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`,
|
||||||
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
|
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
|
||||||
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
|
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
|
||||||
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
|
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
|
||||||
cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`,
|
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
|
||||||
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,
|
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,
|
||||||
cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`,
|
cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`,
|
||||||
cheats_fastDojoRoomDestruction: `Schnelle Dojo-Raum-Zerstörung`,
|
cheats_fastDojoRoomDestruction: `Schnelle Dojo-Raum-Zerstörung`,
|
||||||
|
@ -133,6 +133,7 @@ dict = {
|
|||||||
cheats_unlockExilusEverywhere: `Exilus Adapters Everywhere`,
|
cheats_unlockExilusEverywhere: `Exilus Adapters Everywhere`,
|
||||||
cheats_unlockArcanesEverywhere: `Arcane Adapters Everywhere`,
|
cheats_unlockArcanesEverywhere: `Arcane Adapters Everywhere`,
|
||||||
cheats_noDailyStandingLimits: `No Daily Standing Limits`,
|
cheats_noDailyStandingLimits: `No Daily Standing Limits`,
|
||||||
|
cheats_noDailyFocusLimit: `No Daily Focus Limit`,
|
||||||
cheats_noArgonCrystalDecay: `No Argon Crystal Decay`,
|
cheats_noArgonCrystalDecay: `No Argon Crystal Decay`,
|
||||||
cheats_noMasteryRankUpCooldown: `No Mastery Rank Up Cooldown`,
|
cheats_noMasteryRankUpCooldown: `No Mastery Rank Up Cooldown`,
|
||||||
cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`,
|
cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`,
|
||||||
|
@ -134,6 +134,7 @@ dict = {
|
|||||||
cheats_unlockExilusEverywhere: `Adaptadores Exilus en todas partes`,
|
cheats_unlockExilusEverywhere: `Adaptadores Exilus en todas partes`,
|
||||||
cheats_unlockArcanesEverywhere: `Adaptadores de Arcanos en todas partes`,
|
cheats_unlockArcanesEverywhere: `Adaptadores de Arcanos en todas partes`,
|
||||||
cheats_noDailyStandingLimits: `Sin límite diario de reputación`,
|
cheats_noDailyStandingLimits: `Sin límite diario de reputación`,
|
||||||
|
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`,
|
||||||
cheats_noArgonCrystalDecay: `Sin descomposición de cristal de Argón`,
|
cheats_noArgonCrystalDecay: `Sin descomposición de cristal de Argón`,
|
||||||
cheats_noMasteryRankUpCooldown: `Sin tiempo de espera para rango de maestría`,
|
cheats_noMasteryRankUpCooldown: `Sin tiempo de espera para rango de maestría`,
|
||||||
cheats_noVendorPurchaseLimits: `Sin límite de compras de vendedores`,
|
cheats_noVendorPurchaseLimits: `Sin límite de compras de vendedores`,
|
||||||
|
@ -134,6 +134,7 @@ dict = {
|
|||||||
cheats_unlockExilusEverywhere: `Adaptateurs Exilus partout`,
|
cheats_unlockExilusEverywhere: `Adaptateurs Exilus partout`,
|
||||||
cheats_unlockArcanesEverywhere: `Adaptateur d'Arcanes partout`,
|
cheats_unlockArcanesEverywhere: `Adaptateur d'Arcanes partout`,
|
||||||
cheats_noDailyStandingLimits: `Pas de limite de réputation journalière`,
|
cheats_noDailyStandingLimits: `Pas de limite de réputation journalière`,
|
||||||
|
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`,
|
||||||
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
||||||
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
||||||
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
||||||
|
@ -134,6 +134,7 @@ dict = {
|
|||||||
cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`,
|
cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`,
|
||||||
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
|
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
|
||||||
cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`,
|
cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`,
|
||||||
|
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`,
|
||||||
cheats_noArgonCrystalDecay: `Без распада аргоновых кристаллов`,
|
cheats_noArgonCrystalDecay: `Без распада аргоновых кристаллов`,
|
||||||
cheats_noMasteryRankUpCooldown: `Повышение ранга мастерства без кулдауна`,
|
cheats_noMasteryRankUpCooldown: `Повышение ранга мастерства без кулдауна`,
|
||||||
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
|
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
|
||||||
|
@ -134,6 +134,7 @@ dict = {
|
|||||||
cheats_unlockExilusEverywhere: `全物品自带适配器`,
|
cheats_unlockExilusEverywhere: `全物品自带适配器`,
|
||||||
cheats_unlockArcanesEverywhere: `全物品自带赋能适配器`,
|
cheats_unlockArcanesEverywhere: `全物品自带赋能适配器`,
|
||||||
cheats_noDailyStandingLimits: `无每日声望限制`,
|
cheats_noDailyStandingLimits: `无每日声望限制`,
|
||||||
|
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`,
|
||||||
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
||||||
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
||||||
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user