Compare commits

..

3 Commits

Author SHA1 Message Date
3053112428 chore: auto-generate palladino's vendor manifest (#2160)
A bit ugly, but having the self test forces correctness.

Reviewed-on: OpenWF/SpaceNinjaServer#2160
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-15 05:53:10 -07:00
f448d03880 fix: 1999 bounty chemistry (#2164)
Closes #2162

Reviewed-on: OpenWF/SpaceNinjaServer#2164
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-15 05:05:35 -07:00
d00fbed46f fix: treating chemstry delta as absolute value (#2163)
Reviewed-on: OpenWF/SpaceNinjaServer#2163
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-15 05:05:18 -07:00
4 changed files with 78 additions and 186 deletions

View File

@ -24,7 +24,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
inventory.DialogueHistory.Dialogues ??= []; inventory.DialogueHistory.Dialogues ??= [];
const dialogue = getDialogue(inventory, request.DialogueName); const dialogue = getDialogue(inventory, request.DialogueName);
dialogue.Rank = request.Rank; dialogue.Rank = request.Rank;
dialogue.Chemistry = request.Chemistry; dialogue.Chemistry += request.Chemistry;
dialogue.QueuedDialogues = request.QueuedDialogues; dialogue.QueuedDialogues = request.QueuedDialogues;
for (const bool of request.Booleans) { for (const bool of request.Booleans) {
dialogue.Booleans.push(bool); dialogue.Booleans.push(bool);

View File

@ -66,15 +66,7 @@ import {
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { Loadout } from "../models/inventoryModels/loadoutModel"; import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { import { getLiteSortie, getSortie, idToBountyCycle, idToDay, idToWeek, pushClassicBounties } from "./worldStateService";
getLiteSortie,
getSortie,
getWorldState,
idToBountyCycle,
idToDay,
idToWeek,
pushClassicBounties
} from "./worldStateService";
import { config } from "./configService"; import { config } from "./configService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { ISyndicateMissionInfo } from "../types/worldStateTypes"; import { ISyndicateMissionInfo } from "../types/worldStateTypes";
@ -1266,9 +1258,9 @@ export const addMissionRewards = async (
} }
if (rewardInfo.challengeMissionId) { if (rewardInfo.challengeMissionId) {
const [syndicateTag, tierStr, chemistryStr] = rewardInfo.challengeMissionId.split("_"); const [syndicateTag, tierStr, chemistryBuddyStr] = rewardInfo.challengeMissionId.split("_");
const tier = Number(tierStr); const tier = Number(tierStr);
const chemistry = Number(chemistryStr); const chemistryBuddy = Number(chemistryBuddyStr);
const isSteelPath = missions?.Tier; const isSteelPath = missions?.Tier;
if (syndicateTag === "ZarimanSyndicate") { if (syndicateTag === "ZarimanSyndicate") {
let medallionAmount = tier + 1; let medallionAmount = tier + 1;
@ -1285,22 +1277,19 @@ export const addMissionRewards = async (
if (isSteelPath) standingAmount *= 1.5; if (isSteelPath) standingAmount *= 1.5;
addStanding(inventory, syndicateTag, standingAmount, AffiliationMods); addStanding(inventory, syndicateTag, standingAmount, AffiliationMods);
} }
if (syndicateTag == "HexSyndicate" && chemistry && tier < 6) { if (syndicateTag == "HexSyndicate" && tier < 6) {
const seed = getWorldState().SyndicateMissions.find(x => x.Tag == "HexSyndicate")!.Seed; const buddy = chemistryBuddies[chemistryBuddy];
const { nodes, buddies } = getHexBounties(seed);
const buddy = buddies[tier];
logger.debug(`Hex seed is ${seed}, giving chemistry for ${buddy}`);
if (missions?.Tag != nodes[tier]) {
logger.warn(
`Uh-oh, tier ${tier} bounty should've been on ${nodes[tier]} but you were just on ${missions?.Tag}`
);
}
const tomorrowAt0Utc = config.noKimCooldowns
? Date.now()
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
const dialogue = getDialogue(inventory, buddy); const dialogue = getDialogue(inventory, buddy);
dialogue.Chemistry += chemistry; if (Date.now() >= dialogue.BountyChemExpiry.getTime()) {
dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc); logger.debug(`Giving 20 chemistry for ${buddy}`);
const tomorrowAt0Utc = config.noKimCooldowns
? Date.now()
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
dialogue.Chemistry += 20;
dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc);
} else {
logger.debug(`Already got today's chemistry for ${buddy}`);
}
} }
if (isSteelPath) { if (isSteelPath) {
await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1); await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1);
@ -1864,7 +1853,16 @@ const libraryPersonalTargetToAvatar: Record<string, string> = {
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar" "/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar"
}; };
const node_excluded_buddies: Record<string, string> = { const chemistryBuddies: readonly string[] = [
"/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue"
];
/*const node_excluded_buddies: Record<string, string> = {
SolNode856: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue", SolNode856: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
SolNode852: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue", SolNode852: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
SolNode851: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue", SolNode851: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
@ -1914,4 +1912,4 @@ const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } =>
} }
} }
return { nodes, buddies }; return { nodes, buddies };
}; };*/

View File

@ -6,7 +6,7 @@ import { mixSeeds, SRng } from "@/src/services/rngService";
import { IMongoDate } from "@/src/types/commonTypes"; import { IMongoDate } from "@/src/types/commonTypes";
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportVendors, IRange, IVendor } from "warframe-public-export-plus"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json"; import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json"; import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
@ -21,7 +21,6 @@ import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorIn
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json"; import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json"; import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json"; import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json";
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json"; import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json"; import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
@ -47,7 +46,6 @@ const rawVendorManifests: IVendorManifest[] = [
DuviriAcrithisVendorManifest, DuviriAcrithisVendorManifest,
EntratiLabsEntratiLabsCommisionsManifest, EntratiLabsEntratiLabsCommisionsManifest,
EntratiLabsEntratiLabVendorManifest, EntratiLabsEntratiLabVendorManifest,
HubsIronwakeDondaVendorManifest, // uses preprocessing
HubsRailjackCrewMemberVendorManifest, HubsRailjackCrewMemberVendorManifest,
MaskSalesmanManifest, MaskSalesmanManifest,
Nova1999ConquestShopManifest, Nova1999ConquestShopManifest,
@ -83,10 +81,6 @@ const generatableVendors: IGeneratableVendorInfo[] = [
cycleOffset: 1744934400_000, cycleOffset: 1744934400_000,
cycleDuration: 4 * unixTimesInMs.day cycleDuration: 4 * unixTimesInMs.day
} }
// {
// _id: { $oid: "5dbb4c41e966f7886c3ce939" },
// TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest"
// }
]; ];
const getVendorOid = (typeName: string): string => { const getVendorOid = (typeName: string): string => {
@ -261,13 +255,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration); const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration);
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex)); const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
const manifest = ExportVendors[vendorInfo.TypeName]; const manifest = ExportVendors[vendorInfo.TypeName];
const offersToAdd = []; const offersToAdd: IVendorOffer[] = [];
if ( if (!manifest.isOneBinPerCycle) {
manifest.numItems &&
(manifest.numItems.minValue != manifest.numItems.maxValue ||
manifest.items.length != manifest.numItems.minValue) &&
!manifest.isOneBinPerCycle
) {
const remainingItemCapacity: Record<string, number> = {}; const remainingItemCapacity: Record<string, number> = {};
for (const item of manifest.items) { for (const item of manifest.items) {
remainingItemCapacity[item.storeItem] = 1 + item.duplicates; remainingItemCapacity[item.storeItem] = 1 + item.duplicates;
@ -275,31 +264,48 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
for (const offer of info.ItemManifest) { for (const offer of info.ItemManifest) {
remainingItemCapacity[offer.StoreItem] -= 1; remainingItemCapacity[offer.StoreItem] -= 1;
} }
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue); if (manifest.numItems && manifest.items.length != manifest.numItems.minValue) {
while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
// TODO: Consider per-bin item limits while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
// TODO: Consider item probability weightings // TODO: Consider per-bin item limits
const item = rng.randomElement(manifest.items)!; // TODO: Consider item probability weightings
if (remainingItemCapacity[item.storeItem] != 0) { const item = rng.randomElement(manifest.items)!;
remainingItemCapacity[item.storeItem] -= 1; if (remainingItemCapacity[item.storeItem] != 0) {
offersToAdd.push(item); remainingItemCapacity[item.storeItem] -= 1;
offersToAdd.push(item);
}
} }
} else {
for (const item of manifest.items) {
if (!item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) {
remainingItemCapacity[item.storeItem] -= 1;
offersToAdd.push(item);
}
}
for (const e of Object.entries(remainingItemCapacity)) {
const item = manifest.items.find(x => x.storeItem == e[0])!;
if (!item.alwaysOffered) {
while (e[1] != 0) {
e[1] -= 1;
offersToAdd.push(item);
}
}
}
for (const item of manifest.items) {
if (item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) {
remainingItemCapacity[item.storeItem] -= 1;
offersToAdd.push(item);
}
}
offersToAdd.reverse();
} }
} else { } else {
let binThisCycle; const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
if (manifest.isOneBinPerCycle) {
binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
}
for (const rawItem of manifest.items) { for (const rawItem of manifest.items) {
if (!manifest.isOneBinPerCycle || rawItem.bin == binThisCycle) { if (rawItem.bin == binThisCycle) {
offersToAdd.push(rawItem); offersToAdd.push(rawItem);
} }
} }
// For most vendors, the offers seem to roughly be in reverse order from the manifest. Coda weapons are an odd exception.
if (!manifest.isOneBinPerCycle) {
offersToAdd.reverse();
}
} }
const cycleStart = cycleOffset + cycleIndex * cycleDuration; const cycleStart = cycleOffset + cycleIndex * cycleDuration;
for (const rawItem of offersToAdd) { for (const rawItem of offersToAdd) {
@ -388,4 +394,17 @@ if (isDev) {
) { ) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`); logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
} }
const pall = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest")!
.VendorInfo.ItemManifest;
if (
pall.length != 5 ||
pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" ||
pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" ||
pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" ||
pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack"
) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
}
} }

View File

@ -1,125 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5dbb4c41e966f7886c3ce939"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament",
"ItemPrices": [
{
"ItemCount": 25,
"ItemType": "/Lotus/Types/Items/MiscItems/PrimeBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e945f"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e9468"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits",
"ItemPrices": [
{
"ItemCount": 5,
"ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e9469"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 35000,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e946a"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e946b"
}
}
],
"PropertyTextHash": "62B64A8065B7C0FA345895D4BC234621",
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
}
}
}