feat: track vendor purchases (#1153)

Closes #739

Also adds the `noVendorPurchaseLimits` cheat to disable the logic, which is enabled by default due to lack of vendor rotations.

Reviewed-on: OpenWF/SpaceNinjaServer#1153
This commit is contained in:
Sainan 2025-03-13 02:14:53 -07:00
parent b7800b6d20
commit 6490fadcae
10 changed files with 104 additions and 3 deletions

View File

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

View File

@ -76,7 +76,10 @@ import {
IIncentiveState, IIncentiveState,
ISongChallenge, ISongChallenge,
ILibraryPersonalProgress, ILibraryPersonalProgress,
ICrewShipWeaponDatabase ICrewShipWeaponDatabase,
IRecentVendorPurchaseDatabase,
IVendorPurchaseHistoryEntryDatabase,
IVendorPurchaseHistoryEntryClient
} from "../../types/inventoryTypes/inventoryTypes"; } from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes"; import { IOid } from "../../types/commonTypes";
import { import {
@ -974,6 +977,31 @@ const incentiveStateSchema = new Schema<IIncentiveState>(
{ _id: false } { _id: false }
); );
const vendorPurchaseHistoryEntrySchema = new Schema<IVendorPurchaseHistoryEntryDatabase>(
{
Expiry: Date,
NumPurchased: Number,
ItemId: String
},
{ _id: false }
);
vendorPurchaseHistoryEntrySchema.set("toJSON", {
transform(_doc, obj) {
const db = obj as IVendorPurchaseHistoryEntryDatabase;
const client = obj as IVendorPurchaseHistoryEntryClient;
client.Expiry = toMongoDate(db.Expiry);
}
});
const recentVendorPurchaseSchema = new Schema<IRecentVendorPurchaseDatabase>(
{
VendorType: String,
PurchaseHistory: [vendorPurchaseHistoryEntrySchema]
},
{ _id: false }
);
const collectibleEntrySchema = new Schema<ICollectibleEntry>( const collectibleEntrySchema = new Schema<ICollectibleEntry>(
{ {
CollectibleType: String, CollectibleType: String,
@ -1361,7 +1389,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
RandomUpgradesIdentified: Number, RandomUpgradesIdentified: Number,
BountyScore: Number, BountyScore: Number,
ChallengeInstanceStates: [Schema.Types.Mixed], ChallengeInstanceStates: [Schema.Types.Mixed],
RecentVendorPurchases: [Schema.Types.Mixed], RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined },
Robotics: [Schema.Types.Mixed], Robotics: [Schema.Types.Mixed],
UsedDailyDeals: [Schema.Types.Mixed], UsedDailyDeals: [Schema.Types.Mixed],
CollectibleSeries: { type: [collectibleEntrySchema], default: undefined }, CollectibleSeries: { type: [collectibleEntrySchema], default: undefined },

View File

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

View File

@ -68,6 +68,45 @@ export const handlePurchase = async (
inventoryChanges inventoryChanges
); );
} }
if (!config.noVendorPurchaseLimits) {
inventory.RecentVendorPurchases ??= [];
let vendorPurchases = inventory.RecentVendorPurchases.find(
x => x.VendorType == manifest.VendorInfo.TypeName
);
if (!vendorPurchases) {
vendorPurchases =
inventory.RecentVendorPurchases[
inventory.RecentVendorPurchases.push({
VendorType: manifest.VendorInfo.TypeName,
PurchaseHistory: []
}) - 1
];
}
const historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId);
let numPurchased = purchaseRequest.PurchaseParams.Quantity;
if (historyEntry) {
numPurchased += historyEntry.NumPurchased;
historyEntry.NumPurchased += purchaseRequest.PurchaseParams.Quantity;
} else {
vendorPurchases.PurchaseHistory.push({
ItemId: ItemId,
NumPurchased: purchaseRequest.PurchaseParams.Quantity,
Expiry: new Date(parseInt(offer.Expiry.$date.$numberLong))
});
}
inventoryChanges.RecentVendorPurchases = [
{
VendorType: manifest.VendorInfo.TypeName,
PurchaseHistory: [
{
ItemId: ItemId,
NumPurchased: numPurchased,
Expiry: offer.Expiry
}
]
}
];
}
purchaseRequest.PurchaseParams.Quantity *= offer.QuantityMultiplier; purchaseRequest.PurchaseParams.Quantity *= offer.QuantityMultiplier;
} else if (!ExportVendors[purchaseRequest.PurchaseParams.SourceId!]) { } else if (!ExportVendors[purchaseRequest.PurchaseParams.SourceId!]) {
throw new Error(`unknown vendor: ${purchaseRequest.PurchaseParams.SourceId!}`); throw new Error(`unknown vendor: ${purchaseRequest.PurchaseParams.SourceId!}`);

View File

@ -41,6 +41,7 @@ export interface IInventoryDatabase
| "KubrowPetEggs" | "KubrowPetEggs"
| "PendingCoupon" | "PendingCoupon"
| "Drones" | "Drones"
| "RecentVendorPurchases"
| TEquipmentKey | TEquipmentKey
>, >,
InventoryDatabaseEquipment { InventoryDatabaseEquipment {
@ -67,6 +68,7 @@ export interface IInventoryDatabase
KubrowPetEggs?: IKubrowPetEggDatabase[]; KubrowPetEggs?: IKubrowPetEggDatabase[];
PendingCoupon?: IPendingCouponDatabase; PendingCoupon?: IPendingCouponDatabase;
Drones: IDroneDatabase[]; Drones: IDroneDatabase[];
RecentVendorPurchases?: IRecentVendorPurchaseDatabase[];
} }
export interface IQuestKeyDatabase { export interface IQuestKeyDatabase {
@ -277,7 +279,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
BountyScore: number; BountyScore: number;
ChallengeInstanceStates: IChallengeInstanceState[]; ChallengeInstanceStates: IChallengeInstanceState[];
LoginMilestoneRewards: string[]; LoginMilestoneRewards: string[];
RecentVendorPurchases: Array<number | string>; RecentVendorPurchases?: IRecentVendorPurchaseClient[];
NodeIntrosCompleted: string[]; NodeIntrosCompleted: string[];
GuildId?: IOid; GuildId?: IOid;
CompletedJobChains: ICompletedJobChain[]; CompletedJobChains: ICompletedJobChain[];
@ -361,6 +363,28 @@ export interface IParam {
v: string; v: string;
} }
export interface IRecentVendorPurchaseClient {
VendorType: string;
PurchaseHistory: IVendorPurchaseHistoryEntryClient[];
}
export interface IVendorPurchaseHistoryEntryClient {
Expiry: IMongoDate;
NumPurchased: number;
ItemId: string;
}
export interface IRecentVendorPurchaseDatabase {
VendorType: string;
PurchaseHistory: IVendorPurchaseHistoryEntryDatabase[];
}
export interface IVendorPurchaseHistoryEntryDatabase {
Expiry: Date;
NumPurchased: number;
ItemId: string;
}
export interface IChallengeProgress { export interface IChallengeProgress {
Progress: number; Progress: number;
Name: string; Name: string;

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="noVendorPurchaseLimits" />
<label class="form-check-label" for="noVendorPurchaseLimits" data-loc="cheats_noVendorPurchaseLimits"></label>
</div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="instantResourceExtractorDrones" /> <input class="form-check-input" type="checkbox" id="instantResourceExtractorDrones" />
<label class="form-check-label" for="instantResourceExtractorDrones" data-loc="cheats_instantResourceExtractorDrones"></label> <label class="form-check-label" for="instantResourceExtractorDrones" data-loc="cheats_instantResourceExtractorDrones"></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_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`, cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,
cheats_fastDojoRoomDestruction: `Schnelle Dojo-Raum-Zerstörung`, cheats_fastDojoRoomDestruction: `Schnelle Dojo-Raum-Zerstörung`,

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_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`,
cheats_fastDojoRoomDestruction: `Fast Dojo Room Destruction`, cheats_fastDojoRoomDestruction: `Fast Dojo Room Destruction`,

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_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`,
cheats_fastDojoRoomDestruction: `[UNTRANSLATED] Fast Dojo Room Destruction`, cheats_fastDojoRoomDestruction: `[UNTRANSLATED] Fast Dojo Room Destruction`,

View File

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