feat: argon crystal decay (#1195)

Reviewed-on: OpenWF/SpaceNinjaServer#1195
This commit is contained in:
Sainan 2025-03-15 06:39:54 -07:00
parent 2f59b3d775
commit 2d6e096fde
14 changed files with 81 additions and 17 deletions

View File

@ -29,6 +29,7 @@
"unlockExilusEverywhere": false, "unlockExilusEverywhere": false,
"unlockArcanesEverywhere": false, "unlockArcanesEverywhere": false,
"noDailyStandingLimits": false, "noDailyStandingLimits": false,
"noArgonCrystalDecay": false,
"noVendorPurchaseLimits": true, "noVendorPurchaseLimits": true,
"instantResourceExtractorDrones": false, "instantResourceExtractorDrones": false,
"noDojoRoomBuildStage": false, "noDojoRoomBuildStage": false,

View File

@ -1,5 +1,5 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import allDialogue from "@/static/fixed_responses/allDialogue.json"; import allDialogue from "@/static/fixed_responses/allDialogue.json";
@ -14,12 +14,13 @@ import {
ExportVirtuals ExportVirtuals
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "./infestedFoundryController"; import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "./infestedFoundryController";
import { allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService"; import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger";
export const inventoryController: RequestHandler = async (request, response) => { export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request); const accountId = await getAccountIdForRequest(request);
const inventory = await Inventory.findOne({ accountOwnerId: account._id.toString() }); const inventory = await Inventory.findOne({ accountOwnerId: accountId });
if (!inventory) { if (!inventory) {
response.status(400).json({ error: "inventory was undefined" }); response.status(400).json({ error: "inventory was undefined" });
@ -27,11 +28,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
} }
// Handle daily reset // Handle daily reset
const today: number = Math.trunc(new Date().getTime() / 86400000); if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
if (account.LastLoginDay != today) {
account.LastLoginDay = today;
await account.save();
for (const key of allDailyAffiliationKeys) { for (const key of allDailyAffiliationKeys) {
inventory[key] = 16000 + inventory.PlayerLevel * 500; inventory[key] = 16000 + inventory.PlayerLevel * 500;
} }
@ -39,6 +36,45 @@ export const inventoryController: RequestHandler = async (request, response) =>
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask(); inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
if (inventory.NextRefill) {
if (config.noArgonCrystalDecay) {
inventory.FoundToday = undefined;
} else {
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
const today = Math.trunc(Date.now() / 86400000);
const daysPassed = today - lastLoginDay;
for (let i = 0; i != daysPassed; ++i) {
const numArgonCrystals =
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
?.ItemCount ?? 0;
if (numArgonCrystals == 0) {
break;
}
const numStableArgonCrystals =
inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
?.ItemCount ?? 0;
const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
numArgonCrystals,
numStableArgonCrystals,
numDecayingArgonCrystals,
numDecayingArgonCrystalsToRemove
});
// Remove half of owned decaying argon crystals
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Items/MiscItems/ArgonCrystal",
ItemCount: numDecayingArgonCrystalsToRemove * -1
}
]);
// All stable argon crystals are now decaying
inventory.FoundToday = undefined;
}
}
}
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
await inventory.save(); await inventory.save();
} }
@ -219,9 +255,6 @@ export const getInventoryResponse = async (
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry); applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
} }
// Fix for #380
inventoryResponse.NextRefill = { $date: { $numberLong: "9999999999999" } };
// This determines if the "void fissures" tab is shown in navigation. // This determines if the "void fissures" tab is shown in navigation.
inventoryResponse.HasOwnedVoidProjectionsPreviously = true; inventoryResponse.HasOwnedVoidProjectionsPreviously = true;

View File

@ -1161,7 +1161,8 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
ChallengeProgress: [challengeProgressSchema], ChallengeProgress: [challengeProgressSchema],
//Account Item like Ferrite,Form,Kuva etc //Account Item like Ferrite,Form,Kuva etc
MiscItems: [typeCountSchema], MiscItems: { type: [typeCountSchema], default: [] },
FoundToday: { type: [typeCountSchema], default: undefined },
//Non Upgrade Mods Example:I have 999 item WeaponElectricityDamageMod (only "ItemCount"+"ItemType") //Non Upgrade Mods Example:I have 999 item WeaponElectricityDamageMod (only "ItemCount"+"ItemType")
RawUpgrades: [RawUpgrades], RawUpgrades: [RawUpgrades],
@ -1360,7 +1361,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//https://warframe.fandom.com/wiki/Helminth //https://warframe.fandom.com/wiki/Helminth
InfestedFoundry: infestedFoundrySchema, InfestedFoundry: infestedFoundrySchema,
NextRefill: Schema.Types.Mixed, // Date, convert to IMongoDate NextRefill: { type: Date, default: undefined },
//Purchase this new permanent skin from the Lotus customization options in Personal Quarters located in your Orbiter. //Purchase this new permanent skin from the Lotus customization options in Personal Quarters located in your Orbiter.
//https://warframe.fandom.com/wiki/Lotus#The_New_War //https://warframe.fandom.com/wiki/Lotus#The_New_War
@ -1435,6 +1436,9 @@ inventorySchema.set("toJSON", {
if (inventoryDatabase.BlessingCooldown) { if (inventoryDatabase.BlessingCooldown) {
inventoryResponse.BlessingCooldown = toMongoDate(inventoryDatabase.BlessingCooldown); inventoryResponse.BlessingCooldown = toMongoDate(inventoryDatabase.BlessingCooldown);
} }
if (inventoryDatabase.NextRefill) {
inventoryResponse.NextRefill = toMongoDate(inventoryDatabase.NextRefill);
}
} }
}); });

View File

@ -21,7 +21,6 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
TrackedSettings: { type: [String], default: [] }, TrackedSettings: { type: [String], default: [] },
Nonce: { type: Number, default: 0 }, Nonce: { type: Number, default: 0 },
Dropped: Boolean, Dropped: Boolean,
LastLoginDay: { type: Number },
LatestEventMessageDate: { type: Date, default: 0 } LatestEventMessageDate: { type: Date, default: 0 }
}, },
opts opts

View File

@ -55,6 +55,7 @@ interface IConfig {
unlockExilusEverywhere?: boolean; unlockExilusEverywhere?: boolean;
unlockArcanesEverywhere?: boolean; unlockArcanesEverywhere?: boolean;
noDailyStandingLimits?: boolean; noDailyStandingLimits?: boolean;
noArgonCrystalDecay?: boolean;
noVendorPurchaseLimits?: boolean; noVendorPurchaseLimits?: boolean;
instantResourceExtractorDrones?: boolean; instantResourceExtractorDrones?: boolean;
noDojoRoomBuildStage?: boolean; noDojoRoomBuildStage?: boolean;

View File

@ -1045,6 +1045,22 @@ export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray:
} }
MiscItems[itemIndex].ItemCount += ItemCount; MiscItems[itemIndex].ItemCount += ItemCount;
if (ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal") {
inventory.FoundToday ??= [];
let foundTodayIndex = inventory.FoundToday.findIndex(x => x.ItemType == ItemType);
if (foundTodayIndex == -1) {
foundTodayIndex = inventory.FoundToday.push({ ItemType, ItemCount: 0 }) - 1;
}
inventory.FoundToday[foundTodayIndex].ItemCount += ItemCount;
if (inventory.FoundToday[foundTodayIndex].ItemCount <= 0) {
inventory.FoundToday.splice(foundTodayIndex, 1);
}
if (inventory.FoundToday.length == 0) {
inventory.FoundToday = undefined;
}
}
if (MiscItems[itemIndex].ItemCount == 0) { if (MiscItems[itemIndex].ItemCount == 0) {
MiscItems.splice(itemIndex, 1); MiscItems.splice(itemIndex, 1);
} else if (MiscItems[itemIndex].ItemCount <= 0) { } else if (MiscItems[itemIndex].ItemCount <= 0) {

View File

@ -42,6 +42,7 @@ export interface IInventoryDatabase
| "PendingCoupon" | "PendingCoupon"
| "Drones" | "Drones"
| "RecentVendorPurchases" | "RecentVendorPurchases"
| "NextRefill"
| TEquipmentKey | TEquipmentKey
>, >,
InventoryDatabaseEquipment { InventoryDatabaseEquipment {
@ -69,6 +70,7 @@ export interface IInventoryDatabase
PendingCoupon?: IPendingCouponDatabase; PendingCoupon?: IPendingCouponDatabase;
Drones: IDroneDatabase[]; Drones: IDroneDatabase[];
RecentVendorPurchases?: IRecentVendorPurchaseDatabase[]; RecentVendorPurchases?: IRecentVendorPurchaseDatabase[];
NextRefill?: Date;
} }
export interface IQuestKeyDatabase { export interface IQuestKeyDatabase {
@ -307,7 +309,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
UseAdultOperatorLoadout?: boolean; UseAdultOperatorLoadout?: boolean;
NemesisAbandonedRewards: string[]; NemesisAbandonedRewards: string[];
LastInventorySync: IOid; LastInventorySync: IOid;
NextRefill: IMongoDate; // Next time argon crystals will have a decay tick NextRefill?: IMongoDate;
FoundToday?: IMiscItem[]; // for Argon Crystals FoundToday?: IMiscItem[]; // for Argon Crystals
CustomMarkers?: ICustomMarkers[]; CustomMarkers?: ICustomMarkers[];
ActiveLandscapeTraps: any[]; ActiveLandscapeTraps: any[];

View File

@ -15,7 +15,6 @@ export interface IDatabaseAccount extends IAccountAndLoginResponseCommons {
email: string; email: string;
password: string; password: string;
Dropped?: boolean; Dropped?: boolean;
LastLoginDay?: number;
LatestEventMessageDate: Date; LatestEventMessageDate: Date;
} }

View File

@ -517,6 +517,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="noArgonCrystalDecay" />
<label class="form-check-label" for="noArgonCrystalDecay" data-loc="cheats_noArgonCrystalDecay"></label>
</div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="noVendorPurchaseLimits" /> <input class="form-check-input" type="checkbox" id="noVendorPurchaseLimits" />
<label class="form-check-label" for="noVendorPurchaseLimits" data-loc="cheats_noVendorPurchaseLimits"></label> <label class="form-check-label" for="noVendorPurchaseLimits" data-loc="cheats_noVendorPurchaseLimits"></label>

View File

@ -110,6 +110,7 @@ dict = {
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 Ansehenslimit`,
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`, cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`, cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,

View File

@ -109,6 +109,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_noArgonCrystalDecay: `No Argon Crystal Decay`,
cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`, cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`,
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`, cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`, cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,

View File

@ -110,6 +110,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_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`, cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
cheats_instantResourceExtractorDrones: `Ressources de drone d'extraction instantannées`, cheats_instantResourceExtractorDrones: `Ressources de drone d'extraction instantannées`,
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`, cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,

View File

@ -110,6 +110,7 @@ dict = {
cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`, cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`,
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`, cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`, cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`,
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`, cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`, cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`,
cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`, cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`,

View File

@ -110,6 +110,7 @@ dict = {
cheats_unlockExilusEverywhere: `全物品自带适配器`, cheats_unlockExilusEverywhere: `全物品自带适配器`,
cheats_unlockArcanesEverywhere: `全物品自带赋能适配器`, cheats_unlockArcanesEverywhere: `全物品自带赋能适配器`,
cheats_noDailyStandingLimits: `无每日声望限制`, cheats_noDailyStandingLimits: `无每日声望限制`,
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`, cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
cheats_instantResourceExtractorDrones: `即时资源采集无人机`, cheats_instantResourceExtractorDrones: `即时资源采集无人机`,
cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`, cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`,