Compare commits

..

15 Commits

Author SHA1 Message Date
4acb16e6d6 exclude paintbot
All checks were successful
Build / build (20) (pull_request) Successful in 44s
Build / build (18) (pull_request) Successful in 1m22s
Build / build (22) (pull_request) Successful in 1m14s
2025-04-09 13:29:40 +02:00
c6acab6c11 feat: No Decoration Build Stage cheat
Closes #1502
2025-04-09 13:28:17 +02:00
bb315eaafe chore: handle addItem of crew ship harness (#1516)
All checks were successful
Build / build (18) (push) Successful in 51s
Build / build (20) (push) Successful in 1m19s
Build / build (22) (push) Successful in 1m23s
Build Docker image / docker (push) Successful in 51s
Reviewed-on: #1516
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-09 03:48:07 -07:00
327b834b07 chore: handle zealoid prelate stripped rewards (#1515)
All checks were successful
Build / build (20) (push) Successful in 40s
Build / build (18) (push) Successful in 1m21s
Build Docker image / docker (push) Successful in 37s
Build / build (22) (push) Successful in 1m20s
Reviewed-on: #1515
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-08 03:06:47 -07:00
39be095818 chore: handle season challenge completion in missionInventoryUpdate (#1511)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (18) (push) Has been cancelled
Build / build (20) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Reviewed-on: #1511
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-08 03:06:36 -07:00
ef4973e694 chrore(webui): don't add duplicates to datalists (#1510)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (20) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Browsers will show all options, even if this makes no sense, causing some confusion for users.

Reviewed-on: #1510
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-08 03:06:19 -07:00
7f69667171 feat: dojo component settings (#1509)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (20) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Reviewed-on: #1509
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-08 03:06:06 -07:00
dcdeb0cd34 feat(webui): add pigment (#1507)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (20) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Reviewed-on: #1507
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-08 03:05:53 -07:00
8ce86ad4fd chore(webui): show quantity for recipes (#1506)
All checks were successful
Build / build (20) (push) Successful in 42s
Build / build (22) (push) Successful in 1m17s
Build / build (18) (push) Successful in 1m19s
Build Docker image / docker (push) Successful in 36s
Reviewed-on: #1506
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-07 05:55:33 -07:00
a2f1469779 feat: add attVisualOnly to inbox messages (#1499)
All checks were successful
Build / build (18) (push) Successful in 45s
Build / build (20) (push) Successful in 1m15s
Build / build (22) (push) Successful in 1m24s
Build Docker image / docker (push) Successful in 37s
In case we'll need it...

Reviewed-on: #1499
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-07 05:30:29 -07:00
dd32e082f3 chore: add UmbraDate to equipment (#1496)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (20) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Reviewed-on: #1496
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-07 05:30:15 -07:00
74c7d86090 feat: polychrome (#1495)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (18) (push) Has been cancelled
Build / build (20) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Reviewed-on: #1495
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-07 05:30:00 -07:00
7fd4d50e07 feat(webui): add level keys via "add items" (#1493)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (20) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Reviewed-on: #1493
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-07 05:29:44 -07:00
7f805a1dcc feat: handle KeyToRemove in EOM upload (#1491)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (20) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Reviewed-on: #1491
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-07 05:29:32 -07:00
919f12b8f9 feat: sortie rotation (#1453)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (18) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Build / build (20) (push) Has been cancelled
Reviewed-on: #1453
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-07 05:29:21 -07:00
26 changed files with 604 additions and 169 deletions

View File

@ -34,8 +34,8 @@ export const inboxController: RequestHandler = async (req, res) => {
message.r = true;
await message.save();
const attachmentItems = message.att;
const attachmentCountedItems = message.countedAtt;
const attachmentItems = message.attVisualOnly ? undefined : message.att;
const attachmentCountedItems = message.attVisualOnly ? undefined : message.countedAtt;
if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
res.status(200).end();

View File

@ -63,36 +63,38 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
guild.VaultShipDecorations!.find(x => x.ItemType == itemType)!.ItemCount -= 1;
}
}
if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || config.noDecoBuildStage) {
deco.CompletionTime = new Date();
} else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
let enoughMiscItems = true;
for (const ingredient of meta.ingredients) {
if (
getVaultMiscItemCount(guild, ingredient.ItemType) <
scaleRequiredCount(guild.Tier, ingredient.ItemCount)
) {
enoughMiscItems = false;
break;
}
}
if (enoughMiscItems) {
guild.VaultRegularCredits -= scaleRequiredCount(guild.Tier, meta.price);
deco.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
deco.MiscItems = [];
if (deco.Type != "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco") {
if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || config.noDecoBuildStage) {
deco.CompletionTime = new Date();
} else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
let enoughMiscItems = true;
for (const ingredient of meta.ingredients) {
guild.VaultMiscItems.find(x => x.ItemType == ingredient.ItemType)!.ItemCount -=
scaleRequiredCount(guild.Tier, ingredient.ItemCount);
deco.MiscItems.push({
ItemType: ingredient.ItemType,
ItemCount: scaleRequiredCount(guild.Tier, ingredient.ItemCount)
});
if (
getVaultMiscItemCount(guild, ingredient.ItemType) <
scaleRequiredCount(guild.Tier, ingredient.ItemCount)
) {
enoughMiscItems = false;
break;
}
}
if (enoughMiscItems) {
guild.VaultRegularCredits -= scaleRequiredCount(guild.Tier, meta.price);
deco.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
deco.CompletionTime = new Date(Date.now() + meta.time * 1000);
processDojoBuildMaterialsGathered(guild, meta);
deco.MiscItems = [];
for (const ingredient of meta.ingredients) {
guild.VaultMiscItems.find(x => x.ItemType == ingredient.ItemType)!.ItemCount -=
scaleRequiredCount(guild.Tier, ingredient.ItemCount);
deco.MiscItems.push({
ItemType: ingredient.ItemType,
ItemCount: scaleRequiredCount(guild.Tier, ingredient.ItemCount)
});
}
deco.CompletionTime = new Date(Date.now() + meta.time * 1000);
processDojoBuildMaterialsGathered(guild, meta);
}
}
}
}

View File

@ -0,0 +1,34 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const setDojoComponentColorsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const data = getJSONfromString<ISetDojoComponentColorsRequest>(String(req.body));
const component = guild.DojoComponents.id(data.ComponentId)!;
//const deco = component.Decos!.find(x => x._id.equals(data.DecoId))!;
//deco.Pending = true;
//component.PaintBot = new Types.ObjectId(data.DecoId);
if ("lights" in req.query) {
component.PendingLights = data.Colours;
} else {
component.PendingColors = data.Colours;
}
await guild.save();
res.json(await getDojoClient(guild, 0, component._id));
};
interface ISetDojoComponentColorsRequest {
ComponentId: string;
DecoId: string;
Colours: number[];
}

View File

@ -0,0 +1,25 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const setDojoComponentSettingsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const component = guild.DojoComponents.id(req.query.componentId)!;
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
component.Settings = data.Settings;
await guild.save();
res.json(await getDojoClient(guild, 0, component._id));
};
interface ISetDojoComponentSettingsRequest {
Settings: string;
}

View File

@ -1,10 +1,8 @@
import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addChallenges, addSeasonalChallengeHistory, getInventory } from "@/src/services/inventoryService";
import { addChallenges, getInventory } from "@/src/services/inventoryService";
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportNightwave } from "warframe-public-export-plus";
import { logger } from "@/src/utils/logger";
import { IAffiliationMods } from "@/src/types/purchaseTypes";
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
@ -12,41 +10,19 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "ChallengeProgress SeasonChallengeHistory Affiliations");
let affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress) {
addChallenges(inventory, challenges.ChallengeProgress);
affiliationMods = addChallenges(inventory, challenges.ChallengeProgress, challenges.SeasonChallengeCompletions);
}
if (challenges.SeasonChallengeHistory) {
addSeasonalChallengeHistory(inventory, challenges.SeasonChallengeHistory);
}
const affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress && challenges.SeasonChallengeCompletions) {
for (const challenge of challenges.SeasonChallengeCompletions) {
// Ignore challenges that weren't completed just now
if (!challenges.ChallengeProgress.find(x => challenge.challenge.indexOf(x.Name) != -1)) {
continue;
challenges.SeasonChallengeHistory.forEach(({ challenge, id }) => {
const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
if (itemIndex !== -1) {
inventory.SeasonChallengeHistory[itemIndex].id = id;
} else {
inventory.SeasonChallengeHistory.push({ challenge, id });
}
const meta = ExportNightwave.challenges[challenge.challenge];
logger.debug("Completed challenge", meta);
let affiliation = inventory.Affiliations.find(x => x.Tag == ExportNightwave.affiliationTag);
if (!affiliation) {
affiliation =
inventory.Affiliations[
inventory.Affiliations.push({
Tag: ExportNightwave.affiliationTag,
Standing: 0
}) - 1
];
}
affiliation.Standing += meta.standing;
if (affiliationMods.length == 0) {
affiliationMods.push({ Tag: ExportNightwave.affiliationTag });
}
affiliationMods[0].Standing ??= 0;
affiliationMods[0].Standing += meta.standing;
}
});
}
await inventory.save();

View File

@ -150,9 +150,11 @@ const getItemListsController: RequestHandler = (req, response) => {
if (!item.hidden) {
const resultName = getItemName(item.resultType);
if (resultName) {
let itemName = getString(resultName, lang);
if (item.num > 1) itemName = `${itemName} X ${item.num}`;
res.miscitems.push({
uniqueName: uniqueName,
name: recipeNameTemplate.replace("|ITEM|", getString(resultName, lang))
name: recipeNameTemplate.replace("|ITEM|", itemName)
});
}
}
@ -218,6 +220,11 @@ const getItemListsController: RequestHandler = (req, response) => {
name: getString(key.name || "", lang),
chainLength: key.chainStages.length
});
} else if (key.name) {
res.miscitems.push({
uniqueName,
name: getString(key.name, lang)
});
}
}

View File

@ -10,8 +10,16 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
import { config } from "@/src/services/configService";
import { CRng } from "@/src/services/rngService";
import { ExportNightwave, ExportRegions } from "warframe-public-export-plus";
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
import {
EPOCH,
getSortieTime,
missionTags,
sortieBosses,
sortieBossNode,
sortieBossToFaction,
sortieFactionToFactionIndexes,
sortieFactionToSystemIndexes
} from "@/src/helpers/worlstateHelper";
export const worldStateController: RequestHandler = (req, res) => {
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
@ -27,6 +35,7 @@ export const worldStateController: RequestHandler = (req, res) => {
Time: config.worldState?.lockTime || Math.round(Date.now() / 1000),
Goals: [],
GlobalUpgrades: [],
Sorties: [],
LiteSorties: [],
EndlessXpChoices: [],
SeasonInfo: {
@ -154,6 +163,142 @@ export const worldStateController: RequestHandler = (req, res) => {
});
}
// Sortie cycling every day
{
let genDay;
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 = missionTags[missionIndex];
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
{
const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3];
@ -298,6 +443,7 @@ interface IWorldState {
Goals: IGoal[];
SyndicateMissions: ISyndicateMission[];
GlobalUpgrades: IGlobalUpgrade[];
Sorties: ISortie[];
LiteSorties: ILiteSortie[];
NodeOverrides: INodeOverride[];
EndlessXpChoices: IEndlessXpChoice[];
@ -361,6 +507,20 @@ interface INodeOverride {
CustomNpcEncounters?: string;
}
interface ISortie {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards";
Seed: number;
Boss: string;
Variants: {
missionType: string;
modifierType: string;
node: string;
}[];
}
interface ILiteSortie {
_id: IOid;
Activation: IMongoDate;

View File

@ -0,0 +1,130 @@
export const missionTags = [
"MT_ASSASSINATION",
"MT_EXTERMINATION",
"MT_SURVIVAL",
"MT_RESCUE",
"MT_SABOTAGE",
"MT_CAPTURE",
"MT_COUNTER_INTEL",
"MT_INTEL",
"MT_DEFENSE",
"MT_MOBILE_DEFENSE",
"MT_PVP",
"MT_MASTERY",
"MT_RECOVERY",
"MT_TERRITORY",
"MT_RETRIEVAL",
"MT_HIVE",
"MT_SALVAGE",
"MT_EXCAVATE",
"MT_RAID",
"MT_PURGE",
"MT_GENERIC",
"MT_PURIFY",
"MT_ARENA",
"MT_JUNCTION",
"MT_PURSUIT",
"MT_RACE",
"MT_ASSAULT",
"MT_EVACUATION",
"MT_LANDSCAPE",
"MT_RESOURCE_THEFT",
"MT_ENDLESS_EXTERMINATION",
"MT_ENDLESS_DUVIRI",
"MT_RAILJACK",
"MT_ARTIFACT",
"MT_CORRUPTION",
"MT_VOID_CASCADE",
"MT_ARMAGEDDON",
"MT_VAULTS",
"MT_ALCHEMY",
"MT_ASCENSION",
"MT_ENDLESS_CAPTURE",
"MT_OFFERING",
"MT_PVPVE"
];
export const sortieBosses = [
"SORTIE_BOSS_HYENA",
"SORTIE_BOSS_KELA",
"SORTIE_BOSS_VOR",
"SORTIE_BOSS_RUK",
"SORTIE_BOSS_HEK",
"SORTIE_BOSS_KRIL",
"SORTIE_BOSS_TYL",
"SORTIE_BOSS_JACKAL",
"SORTIE_BOSS_ALAD",
"SORTIE_BOSS_AMBULAS",
"SORTIE_BOSS_NEF",
"SORTIE_BOSS_RAPTOR",
"SORTIE_BOSS_PHORID",
"SORTIE_BOSS_LEPHANTIS",
"SORTIE_BOSS_INFALAD",
"SORTIE_BOSS_CORRUPTED_VOR"
];
export const sortieBossToFaction: Record<string, string> = {
SORTIE_BOSS_HYENA: "FC_CORPUS",
SORTIE_BOSS_KELA: "FC_GRINEER",
SORTIE_BOSS_VOR: "FC_GRINEER",
SORTIE_BOSS_RUK: "FC_GRINEER",
SORTIE_BOSS_HEK: "FC_GRINEER",
SORTIE_BOSS_KRIL: "FC_GRINEER",
SORTIE_BOSS_TYL: "FC_GRINEER",
SORTIE_BOSS_JACKAL: "FC_CORPUS",
SORTIE_BOSS_ALAD: "FC_CORPUS",
SORTIE_BOSS_AMBULAS: "FC_CORPUS",
SORTIE_BOSS_NEF: "FC_CORPUS",
SORTIE_BOSS_RAPTOR: "FC_CORPUS",
SORTIE_BOSS_PHORID: "FC_INFESTATION",
SORTIE_BOSS_LEPHANTIS: "FC_INFESTATION",
SORTIE_BOSS_INFALAD: "FC_INFESTATION",
SORTIE_BOSS_CORRUPTED_VOR: "FC_CORRUPTED"
};
export const sortieFactionToSystemIndexes: Record<string, number[]> = {
FC_GRINEER: [0, 2, 3, 5, 6, 9, 11, 18],
FC_CORPUS: [1, 4, 7, 8, 12, 15],
FC_INFESTATION: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15],
FC_CORRUPTED: [14]
};
export const sortieFactionToFactionIndexes: Record<string, number[]> = {
FC_GRINEER: [0],
FC_CORPUS: [1],
FC_INFESTATION: [0, 1, 2],
FC_CORRUPTED: [3]
};
export const sortieBossNode: Record<string, string> = {
SORTIE_BOSS_HYENA: "SolNode127",
SORTIE_BOSS_KELA: "SolNode193",
SORTIE_BOSS_VOR: "SolNode108",
SORTIE_BOSS_RUK: "SolNode32",
SORTIE_BOSS_HEK: "SolNode24",
SORTIE_BOSS_KRIL: "SolNode99",
SORTIE_BOSS_TYL: "SolNode105",
SORTIE_BOSS_JACKAL: "SolNode104",
SORTIE_BOSS_ALAD: "SolNode53",
SORTIE_BOSS_AMBULAS: "SolNode51",
SORTIE_BOSS_NEF: "SettlementNode20",
SORTIE_BOSS_RAPTOR: "SolNode210",
SORTIE_BOSS_LEPHANTIS: "SolNode712",
SORTIE_BOSS_INFALAD: "SolNode705"
};
export const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
export const getSortieTime = (day: number): number => {
const dayStart = EPOCH + day * 86400000;
const date = new Date(dayStart);
date.setUTCHours(12);
const isDst = new Intl.DateTimeFormat("en-US", {
timeZone: "America/Toronto",
timeZoneName: "short"
})
.formatToParts(date)
.find(part => part.type === "timeZoneName")!
.value.includes("DT");
return dayStart + (isDst ? 16 : 17) * 3600000;
};

View File

@ -29,7 +29,8 @@ const dojoDecoSchema = new Schema<IDojoDecoDatabase>({
MiscItems: { type: [typeCountSchema], default: undefined },
CompletionTime: Date,
RushPlatinum: Number,
PictureFrameInfo: pictureFrameInfoSchema
PictureFrameInfo: pictureFrameInfoSchema,
Pending: Boolean
});
const dojoLeaderboardEntrySchema = new Schema<IDojoLeaderboardEntry>(
@ -57,6 +58,12 @@ const dojoComponentSchema = new Schema<IDojoComponentDatabase>({
DestructionTime: Date,
Decos: [dojoDecoSchema],
DecoCapacity: Number,
PaintBot: Schema.Types.ObjectId,
PendingColors: { type: [Number], default: undefined },
Colors: { type: [Number], default: undefined },
PendingLights: { type: [Number], default: undefined },
Lights: { type: [Number], default: undefined },
Settings: String,
Leaderboard: { type: [dojoLeaderboardEntrySchema], default: undefined }
});

View File

@ -4,7 +4,8 @@ import { typeCountSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
export interface IMessageClient extends Omit<IMessageDatabase, "_id" | "date" | "startDate" | "endDate" | "ownerId"> {
export interface IMessageClient
extends Omit<IMessageDatabase, "_id" | "date" | "startDate" | "endDate" | "ownerId" | "attVisualOnly"> {
_id?: IOid;
date: IMongoDate;
startDate?: IMongoDate;
@ -29,6 +30,7 @@ export interface IMessage {
endDate?: Date;
att?: string[];
countedAtt?: ITypeCount[];
attVisualOnly?: boolean;
transmission?: string;
arg?: Arg[];
gifts?: IGift[];
@ -108,6 +110,7 @@ const messageSchema = new Schema<IMessageDatabase>(
att: { type: [String], default: undefined },
gifts: { type: [giftSchema], default: undefined },
countedAtt: { type: [typeCountSchema], default: undefined },
attVisualOnly: Boolean,
transmission: String,
arg: {
type: [
@ -141,6 +144,7 @@ messageSchema.set("toJSON", {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.attVisualOnly;
messageClient.date = toMongoDate(messageDatabase.date);

View File

@ -872,6 +872,7 @@ const EquipmentSchema = new Schema<IEquipmentDatabase>(
OffensiveUpgrade: String,
DefensiveUpgrade: String,
UpgradesExpiry: Date,
UmbraDate: Date,
ArchonCrystalUpgrades: { type: [ArchonCrystalUpgradeSchema], default: undefined },
Weapon: crewShipWeaponSchema,
Customization: crewShipCustomizationSchema,
@ -902,6 +903,9 @@ EquipmentSchema.set("toJSON", {
if (db.UpgradesExpiry) {
client.UpgradesExpiry = toMongoDate(db.UpgradesExpiry);
}
if (db.UmbraDate) {
client.UmbraDate = toMongoDate(db.UmbraDate);
}
}
});

View File

@ -105,7 +105,9 @@ import { setActiveQuestController } from "@/src/controllers/api/setActiveQuestCo
import { setActiveShipController } from "@/src/controllers/api/setActiveShipController";
import { setAllianceGuildPermissionsController } from "@/src/controllers/api/setAllianceGuildPermissionsController";
import { setBootLocationController } from "@/src/controllers/api/setBootLocationController";
import { setDojoComponentColorsController } from "@/src/controllers/api/setDojoComponentColorsController";
import { setDojoComponentMessageController } from "@/src/controllers/api/setDojoComponentMessageController";
import { setDojoComponentSettingsController } from "@/src/controllers/api/setDojoComponentSettingsController";
import { setEquippedInstrumentController } from "@/src/controllers/api/setEquippedInstrumentController";
import { setGuildMotdController } from "@/src/controllers/api/setGuildMotdController";
import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDecoInfoController";
@ -261,7 +263,9 @@ apiRouter.post("/saveLoadout.php", saveLoadoutController);
apiRouter.post("/saveSettings.php", saveSettingsController);
apiRouter.post("/saveVaultAutoContribute.php", saveVaultAutoContributeController);
apiRouter.post("/sell.php", sellController);
apiRouter.post("/setDojoComponentColors.php", setDojoComponentColorsController);
apiRouter.post("/setDojoComponentMessage.php", setDojoComponentMessageController);
apiRouter.post("/setDojoComponentSettings.php", setDojoComponentSettingsController);
apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController);
apiRouter.post("/setGuildMotd.php", setGuildMotdController);
apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController);

View File

@ -141,6 +141,7 @@ export const getDojoClient = async (
DojoComponents: []
};
const roomsToRemove: Types.ObjectId[] = [];
const decosToRemoveNoRefund: { componentId: Types.ObjectId; decoId: Types.ObjectId }[] = [];
let needSave = false;
for (const dojoComponent of guild.DojoComponents) {
if (!componentId || dojoComponent._id.equals(componentId)) {
@ -150,7 +151,8 @@ export const getDojoClient = async (
ppf: dojoComponent.ppf,
Name: dojoComponent.Name,
Message: dojoComponent.Message,
DecoCapacity: dojoComponent.DecoCapacity ?? 600
DecoCapacity: dojoComponent.DecoCapacity ?? 600,
Settings: dojoComponent.Settings
};
if (dojoComponent.pi) {
clientComponent.pi = toOid(dojoComponent.pi);
@ -212,6 +214,21 @@ export const getDojoClient = async (
PictureFrameInfo: deco.PictureFrameInfo
};
if (deco.CompletionTime) {
if (
deco.Type == "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco" &&
Date.now() >= deco.CompletionTime.getTime()
) {
if (dojoComponent.PendingColors) {
dojoComponent.Colors = dojoComponent.PendingColors;
dojoComponent.PendingColors = undefined;
}
if (dojoComponent.PendingLights) {
dojoComponent.Lights = dojoComponent.PendingLights;
dojoComponent.PendingLights = undefined;
}
decosToRemoveNoRefund.push({ componentId: dojoComponent._id, decoId: deco._id });
continue;
}
clientDeco.CompletionTime = toMongoDate(deco.CompletionTime);
} else {
clientDeco.RegularCredits = deco.RegularCredits;
@ -220,6 +237,10 @@ export const getDojoClient = async (
clientComponent.Decos.push(clientDeco);
}
}
clientComponent.PendingColors = dojoComponent.PendingColors;
clientComponent.Colors = dojoComponent.Colors;
clientComponent.PendingLights = dojoComponent.PendingLights;
clientComponent.Lights = dojoComponent.Lights;
dojo.DojoComponents.push(clientComponent);
}
}
@ -230,6 +251,15 @@ export const getDojoClient = async (
}
needSave = true;
}
for (const deco of decosToRemoveNoRefund) {
logger.debug(`removing polychrome`, deco);
const component = guild.DojoComponents.id(deco.componentId)!;
component.Decos!.splice(
component.Decos!.findIndex(x => x._id.equals(deco.decoId)),
1
);
needSave = true;
}
if (needSave) {
await guild.save();
}

View File

@ -54,6 +54,7 @@ const convertEquipment = (client: IEquipmentClient): IEquipmentDatabase => {
InfestationDate: convertOptionalDate(client.InfestationDate),
Expiry: convertOptionalDate(client.Expiry),
UpgradesExpiry: convertOptionalDate(client.UpgradesExpiry),
UmbraDate: convertOptionalDate(client.UmbraDate),
CrewMembers: client.CrewMembers ? convertCrewShipMembers(client.CrewMembers) : undefined,
Details: client.Details ? convertKubrowDetails(client.Details) : undefined,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition

View File

@ -1,7 +1,7 @@
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { config } from "@/src/services/configService";
import { Types } from "mongoose";
import { SlotNames, IInventoryChanges, IBinChanges, slotNames } from "@/src/types/purchaseTypes";
import { SlotNames, IInventoryChanges, IBinChanges, slotNames, IAffiliationMods } from "@/src/types/purchaseTypes";
import {
IChallengeProgress,
IFlavourItem,
@ -45,6 +45,7 @@ import {
ExportGear,
ExportKeys,
ExportMisc,
ExportNightwave,
ExportRailjackWeapons,
ExportRecipes,
ExportResources,
@ -538,15 +539,9 @@ export const addItem = async (
if (!key) return {};
return { QuestKeys: [key] };
} else {
const key = { ItemType: typeName, ItemCount: quantity };
const index = inventory.LevelKeys.findIndex(levelKey => levelKey.ItemType == typeName);
if (index != -1) {
inventory.LevelKeys[index].ItemCount += quantity;
} else {
inventory.LevelKeys.push(key);
}
return { LevelKeys: [key] };
const levelKeyChanges = [{ ItemType: typeName, ItemCount: quantity }];
addLevelKeys(inventory, levelKeyChanges);
return { LevelKeys: levelKeyChanges };
}
}
if (typeName in ExportDrones) {
@ -664,6 +659,8 @@ export const addItem = async (
return {
MiscItems: miscItemChanges
};
} else if (typeName == "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness") {
return addCrewShipHarness(inventory, typeName);
}
break;
}
@ -1240,6 +1237,10 @@ export const addRecipes = (inventory: TInventoryDatabaseDocument, itemsArray: IT
applyArrayChanges(inventory.Recipes, itemsArray);
};
export const addLevelKeys = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
applyArrayChanges(inventory.LevelKeys, itemsArray);
};
export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawUpgrade[]): void => {
const { RawUpgrades } = inventory;
@ -1304,35 +1305,52 @@ export const addFocusXpIncreases = (inventory: TInventoryDatabaseDocument, focus
inventory.DailyFocus -= focusXpPlus.reduce((a, b) => a + b, 0);
};
export const addSeasonalChallengeHistory = (
export const addChallenges = (
inventory: TInventoryDatabaseDocument,
itemsArray: ISeasonChallenge[]
): void => {
const category = inventory.SeasonChallengeHistory;
itemsArray.forEach(({ challenge, id }) => {
const itemIndex = category.findIndex(i => i.challenge === challenge);
ChallengeProgress: IChallengeProgress[],
SeasonChallengeCompletions: ISeasonChallenge[] | undefined
): IAffiliationMods[] => {
ChallengeProgress.forEach(({ Name, Progress }) => {
const itemIndex = inventory.ChallengeProgress.findIndex(i => i.Name === Name);
if (itemIndex !== -1) {
category[itemIndex].id = id;
inventory.ChallengeProgress[itemIndex].Progress = Progress;
} else {
category.push({ challenge, id });
inventory.ChallengeProgress.push({ Name, Progress });
}
});
};
export const addChallenges = (inventory: TInventoryDatabaseDocument, itemsArray: IChallengeProgress[]): void => {
const category = inventory.ChallengeProgress;
const affiliationMods: IAffiliationMods[] = [];
if (SeasonChallengeCompletions) {
for (const challenge of SeasonChallengeCompletions) {
// Ignore challenges that weren't completed just now
if (!ChallengeProgress.find(x => challenge.challenge.indexOf(x.Name) != -1)) {
continue;
}
itemsArray.forEach(({ Name, Progress }) => {
const itemIndex = category.findIndex(i => i.Name === Name);
const meta = ExportNightwave.challenges[challenge.challenge];
logger.debug("Completed challenge", meta);
if (itemIndex !== -1) {
category[itemIndex].Progress = Progress;
} else {
category.push({ Name, Progress });
let affiliation = inventory.Affiliations.find(x => x.Tag == ExportNightwave.affiliationTag);
if (!affiliation) {
affiliation =
inventory.Affiliations[
inventory.Affiliations.push({
Tag: ExportNightwave.affiliationTag,
Standing: 0
}) - 1
];
}
affiliation.Standing += meta.standing;
if (affiliationMods.length == 0) {
affiliationMods.push({ Tag: ExportNightwave.affiliationTag });
}
affiliationMods[0].Standing ??= 0;
affiliationMods[0].Standing += meta.standing;
}
});
}
return affiliationMods;
};
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes }: IMission): void => {

View File

@ -22,6 +22,7 @@ import {
addFusionTreasures,
addGearExpByCategory,
addItem,
addLevelKeys,
addMiscItems,
addMissionComplete,
addMods,
@ -77,19 +78,52 @@ export const addMissionInventoryUpdates = async (
inventoryUpdates: IMissionInventoryUpdateRequest
): Promise<IInventoryChanges> => {
const inventoryChanges: IInventoryChanges = {};
if (
inventoryUpdates.EndOfMatchUpload &&
inventoryUpdates.Missions &&
inventoryUpdates.Missions.Tag in ExportRegions
) {
const node = ExportRegions[inventoryUpdates.Missions.Tag];
if (node.miscItemFee) {
addMiscItems(inventory, [
{
ItemType: node.miscItemFee.ItemType,
ItemCount: node.miscItemFee.ItemCount * -1
}
]);
if (inventoryUpdates.EndOfMatchUpload) {
if (inventoryUpdates.Missions && inventoryUpdates.Missions.Tag in ExportRegions) {
const node = ExportRegions[inventoryUpdates.Missions.Tag];
if (node.miscItemFee) {
addMiscItems(inventory, [
{
ItemType: node.miscItemFee.ItemType,
ItemCount: node.miscItemFee.ItemCount * -1
}
]);
}
}
if (inventoryUpdates.KeyToRemove) {
if (!inventoryUpdates.KeyOwner || inventory.accountOwnerId.equals(inventoryUpdates.KeyOwner)) {
addLevelKeys(inventory, [
{
ItemType: inventoryUpdates.KeyToRemove,
ItemCount: -1
}
]);
}
}
if (
inventoryUpdates.MissionFailed &&
inventoryUpdates.MissionStatus == "GS_FAILURE" &&
inventoryUpdates.ObjectiveReached &&
!inventoryUpdates.LockedWeaponGroup
) {
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!;
const SuitId = new Types.ObjectId(config.s!.ItemId.$oid);
inventory.BrandedSuits ??= [];
if (!inventory.BrandedSuits.find(x => x.equals(SuitId))) {
inventory.BrandedSuits.push(SuitId);
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/BrandedMessage",
sub: "/Lotus/Language/G1Quests/BrandedTitle",
att: ["/Lotus/Types/Recipes/Components/BrandRemovalBlueprint"],
highPriority: true // TOVERIFY: I cannot find any content of this within the last 10 years so I can only assume that highPriority is set (it certainly would make sense), but I just don't know for sure that it is so on live.
}
]);
}
}
}
if (inventoryUpdates.RewardInfo) {
@ -110,32 +144,6 @@ export const addMissionInventoryUpdates = async (
inventory.NemesisAbandonedRewards = inventoryUpdates.RewardInfo.NemesisAbandonedRewards;
}
}
if (
inventoryUpdates.MissionFailed &&
inventoryUpdates.MissionStatus == "GS_FAILURE" &&
inventoryUpdates.EndOfMatchUpload &&
inventoryUpdates.ObjectiveReached &&
!inventoryUpdates.LockedWeaponGroup
) {
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!;
const SuitId = new Types.ObjectId(config.s!.ItemId.$oid);
inventory.BrandedSuits ??= [];
if (!inventory.BrandedSuits.find(x => x.equals(SuitId))) {
inventory.BrandedSuits.push(SuitId);
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/BrandedMessage",
sub: "/Lotus/Language/G1Quests/BrandedTitle",
att: ["/Lotus/Types/Recipes/Components/BrandRemovalBlueprint"],
highPriority: true // TOVERIFY: I cannot find any content of this within the last 10 years so I can only assume that highPriority is set (it certainly would make sense), but I just don't know for sure that it is so on live.
}
]);
}
}
for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) {
if (value === undefined) {
logger.error(`Inventory update key ${key} has no value `);
@ -187,7 +195,7 @@ export const addMissionInventoryUpdates = async (
addRecipes(inventory, value);
break;
case "ChallengeProgress":
addChallenges(inventory, value);
addChallenges(inventory, value, inventoryUpdates.SeasonChallengeCompletions);
break;
case "FusionTreasures":
addFusionTreasures(inventory, value);
@ -594,10 +602,24 @@ export const addMissionRewards = async (
if (!droptable) {
logger.error(`unknown droptable ${si.DropTable}`);
} else {
for (let i = 0; i != si.DROP_MOD.length; ++i) {
for (const pool of droptable) {
const reward = getRandomReward(pool.items)!;
logger.debug(`stripped droptable rolled`, reward);
const modsPool = droptable[0].items;
const blueprintsPool = (droptable.length > 1 ? droptable[1] : droptable[0]).items;
if (si.DROP_MOD) {
for (let i = 0; i != si.DROP_MOD.length; ++i) {
const reward = getRandomReward(modsPool)!;
logger.debug(`stripped droptable (mods pool) rolled`, reward);
await addItem(inventory, reward.type);
MissionRewards.push({
StoreItem: toStoreItem(reward.type),
ItemCount: 1,
FromEnemyCache: true // to show "identified"
});
}
}
if (si.DROP_BLUEPRINT) {
for (let i = 0; i != si.DROP_BLUEPRINT.length; ++i) {
const reward = getRandomReward(blueprintsPool)!;
logger.debug(`stripped droptable (blueprints pool) rolled`, reward);
await addItem(inventory, reward.type);
MissionRewards.push({
StoreItem: toStoreItem(reward.type),

View File

@ -161,6 +161,7 @@ export interface IDojoClient {
export interface IDojoComponentClient {
id: IOid;
SortId?: IOid;
pf: string; // Prefab (.level)
ppf: string;
pi?: IOid; // Parent ID. N/A to root.
@ -175,16 +176,26 @@ export interface IDojoComponentClient {
DestructionTime?: IMongoDate;
Decos?: IDojoDecoClient[];
DecoCapacity?: number;
PaintBot?: IOid;
PendingColors?: number[];
Colors?: number[];
PendingLights?: number[];
Lights?: number[];
Settings?: string;
}
export interface IDojoComponentDatabase
extends Omit<IDojoComponentClient, "id" | "pi" | "CompletionTime" | "DestructionTime" | "Decos"> {
extends Omit<
IDojoComponentClient,
"id" | "SortId" | "pi" | "CompletionTime" | "DestructionTime" | "Decos" | "PaintBot"
> {
_id: Types.ObjectId;
pi?: Types.ObjectId;
CompletionTime?: Date;
CompletionLogPending?: boolean;
DestructionTime?: Date;
Decos?: IDojoDecoDatabase[];
PaintBot?: Types.ObjectId;
Leaderboard?: IDojoLeaderboardEntry[];
}
@ -200,6 +211,7 @@ export interface IDojoDecoClient {
CompletionTime?: IMongoDate;
RushPlatinum?: number;
PictureFrameInfo?: IPictureFrameInfo;
Pending?: boolean;
}
export interface IDojoDecoDatabase extends Omit<IDojoDecoClient, "id" | "CompletionTime"> {

View File

@ -90,12 +90,13 @@ export interface IEquipmentSelection {
export interface IEquipmentClient
extends Omit<
IEquipmentDatabase,
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "CrewMembers" | "Details"
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "CrewMembers" | "Details"
> {
ItemId: IOid;
InfestationDate?: IMongoDate;
Expiry?: IMongoDate;
UpgradesExpiry?: IMongoDate;
UmbraDate?: IMongoDate;
CrewMembers?: ICrewShipMembersClient;
Details?: IKubrowPetDetailsClient;
}
@ -134,6 +135,7 @@ export interface IEquipmentDatabase {
OffensiveUpgrade?: string;
DefensiveUpgrade?: string;
UpgradesExpiry?: Date;
UmbraDate?: Date; // related to scrapped "echoes of umbra" feature
ArchonCrystalUpgrades?: IArchonCrystalUpgrade[];
Weapon?: ICrewShipWeapon;
Customization?: ICrewShipCustomization;

View File

@ -49,6 +49,9 @@ export type IMissionInventoryUpdateRequest = {
rewardsMultiplier?: number;
GoalTag: string;
LevelKeyName: string;
KeyOwner?: string;
KeyRemovalHash?: string;
KeyToRemove?: string;
ActiveBoosters?: IBooster[];
RawUpgrades?: IRawUpgrade[];
FusionTreasures?: IFusionTreasure[];
@ -98,7 +101,8 @@ export type IMissionInventoryUpdateRequest = {
Upgrades?: IUpgradeClient[]; // riven challenge progress
StrippedItems?: {
DropTable: string;
DROP_MOD: number[];
DROP_MOD?: number[];
DROP_BLUEPRINT?: number[];
}[];
DeathMarks?: string[];
Nemesis?: number;

View File

@ -62,23 +62,6 @@
}
}
],
"Sorties": [
{
"_id": { "$oid": "663a4c7d4d932c97c0a3acd7" },
"Activation": { "$date": { "$numberLong": "1715097600000" } },
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
"Reward": "/Lotus/Types/Game/MissionDecks/SortieRewards",
"Seed": 24491,
"Boss": "SORTIE_BOSS_TYL",
"ExtraDrops": [],
"Variants": [
{ "missionType": "MT_TERRITORY", "modifierType": "SORTIE_MODIFIER_ARMOR", "node": "SolNode122", "tileset": "GrineerOceanTileset" },
{ "missionType": "MT_MOBILE_DEFENSE", "modifierType": "SORTIE_MODIFIER_LOW_ENERGY", "node": "SolNode184", "tileset": "GrineerGalleonTileset" },
{ "missionType": "MT_LANDSCAPE", "modifierType": "SORTIE_MODIFIER_EXIMUS", "node": "SolNode228", "tileset": "EidolonTileset" }
],
"Twitter": true
}
],
"SyndicateMissions": [
{
"_id": { "$oid": "663a4fc5ba6f84724fa48049" },

View File

@ -221,6 +221,11 @@ function fetchItemList() {
name: loc("code_zanuka")
});
data.miscitems.push({
uniqueName: "/Lotus/Types/Items/Research/DojoColors/GenericDojoColorPigment",
name: loc("code_pigment")
});
const itemMap = {
// Generics for rivens
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: loc("code_archgun") },
@ -260,6 +265,7 @@ function fetchItemList() {
} else if (type == "uniqueLevelCaps") {
uniqueLevelCaps = items;
} else {
const nameSet = new Set();
items.forEach(item => {
if (item.name.includes("<ARCHWING> ")) {
item.name = item.name.replace("<ARCHWING> ", "");
@ -306,20 +312,19 @@ function fetchItemList() {
document
.getElementById("datalist-" + type + "-" + item.partType.slice(5))
.appendChild(option);
}
} else if (item.badReason != "notraw") {
if (nameSet.has(item.name)) {
//console.log(`Not adding ${item.uniqueName} to datalist for ${type} due to duplicate display name: ${item.name}`);
} else {
console.log(item.partType);
nameSet.add(item.name);
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option);
}
}
if (item.badReason != "notraw") {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option);
}
itemMap[item.uniqueName] = { ...item, type };
});
}

View File

@ -53,6 +53,7 @@ dict = {
code_setInactive: `Quest inaktiv setzen`,
code_completed: `Abgeschlossen`,
code_active: `Aktiv`,
code_pigment: `Pigment`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
login_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`,

View File

@ -52,6 +52,7 @@ dict = {
code_setInactive: `Make the quest inactive`,
code_completed: `Completed`,
code_active: `Active`,
code_pigment: `Pigment`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
login_emailLabel: `Email address`,
login_passwordLabel: `Password`,

View File

@ -53,6 +53,7 @@ dict = {
code_setInactive: `[UNTRANSLATED] Make the quest inactive`,
code_completed: `[UNTRANSLATED] Completed`,
code_active: `[UNTRANSLATED] Active`,
code_pigment: `Pigment`,
login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`,

View File

@ -53,6 +53,7 @@ dict = {
code_setInactive: `Сделать квест неактивным`,
code_completed: `Завершено`,
code_active: `Активный`,
code_pigment: `Пигмент`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
login_emailLabel: `Адрес электронной почты`,
login_passwordLabel: `Пароль`,

View File

@ -53,6 +53,7 @@ dict = {
code_setInactive: `[UNTRANSLATED] Make the quest inactive`,
code_completed: `[UNTRANSLATED] Completed`,
code_active: `[UNTRANSLATED] Active`,
code_pigment: `颜料`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`,
login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`,