diff --git a/src/controllers/api/feedPrinceController.ts b/src/controllers/api/feedPrinceController.ts new file mode 100644 index 00000000..89c6fce6 --- /dev/null +++ b/src/controllers/api/feedPrinceController.ts @@ -0,0 +1,39 @@ +import type { RequestHandler } from "express"; +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { addMiscItem, getInventory } from "../../services/inventoryService.ts"; +import { getJSONfromString } from "../../helpers/stringHelpers.ts"; +import { logger } from "../../utils/logger.ts"; + +export const feedPrinceController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "MiscItems NokkoColony"); + const payload = getJSONfromString(String(req.body)); + + switch (payload.Mode) { + case "r": { + const InventoryChanges = {}; + addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/MushroomFood", payload.Amount * -1, InventoryChanges); + inventory.NokkoColony ??= { + FeedLevel: 0, + JournalEntries: [] + }; + inventory.NokkoColony.FeedLevel += payload.Amount; + await inventory.save(); + res.json({ + FeedSucceeded: true, + FeedLevel: inventory.NokkoColony.FeedLevel, + InventoryChanges + }); + break; + } + + default: + logger.debug(`data provided to ${req.path}: ${String(req.body)}`); + throw new Error(`unknown feedPrince mode: ${payload.Mode}`); + } +}; + +interface IFeedPrinceRequest { + Mode: string; // r + Amount: number; +} diff --git a/src/controllers/api/researchMushroomController.ts b/src/controllers/api/researchMushroomController.ts new file mode 100644 index 00000000..727fe234 --- /dev/null +++ b/src/controllers/api/researchMushroomController.ts @@ -0,0 +1,117 @@ +import type { RequestHandler } from "express"; +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { addMiscItem, getInventory } from "../../services/inventoryService.ts"; +import { getJSONfromString } from "../../helpers/stringHelpers.ts"; +import { logger } from "../../utils/logger.ts"; +import type { IJournalEntry } from "../../types/inventoryTypes/inventoryTypes.ts"; +import type { IAffiliationMods } from "../../types/purchaseTypes.ts"; + +export const researchMushroomController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "MiscItems NokkoColony Affiliations"); + const payload = getJSONfromString(String(req.body)); + switch (payload.Mode) { + case "r": { + const InventoryChanges = {}; + const AffiliationMods: IAffiliationMods[] = []; + + addMiscItem(inventory, payload.MushroomItem, payload.Amount * -1, InventoryChanges); + if (payload.Convert) { + addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/MushroomFood", payload.Amount, InventoryChanges); + } + + inventory.NokkoColony ??= { + FeedLevel: 0, + JournalEntries: [] + }; + + let journalEntry = inventory.NokkoColony.JournalEntries.find(x => x.EntryType == payload.MushroomItem); + if (!journalEntry) { + journalEntry = { EntryType: payload.MushroomItem, Progress: 0 }; + inventory.NokkoColony.JournalEntries.push(journalEntry); + } + + let syndicate = inventory.Affiliations.find(x => x.Tag == "NightcapJournalSyndicate"); + if (!syndicate) { + syndicate = { Tag: "NightcapJournalSyndicate", Title: 0, Standing: 0 }; + inventory.Affiliations.push(syndicate); + } + const completedBefore = inventory.NokkoColony.JournalEntries.filter( + entry => getJournalRank(entry) === 3 + ).length; + const PrevRank = syndicateTitleThresholds.reduce( + (rank, threshold, i) => (completedBefore >= threshold ? i : rank), + 0 + ); + + if (getJournalRank(journalEntry) < 3) journalEntry.Progress += payload.Amount; + + const completedAfter = inventory.NokkoColony.JournalEntries.filter( + entry => getJournalRank(entry) === 3 + ).length; + const NewRank = syndicateTitleThresholds.reduce( + (rank, threshold, i) => (completedAfter >= threshold ? i : rank), + 0 + ); + + if (NewRank > (syndicate.Title ?? 0)) { + syndicate.Title = NewRank; + AffiliationMods.push({ Tag: "NightcapJournalSyndicate", Title: NewRank }); + } + + await inventory.save(); + res.json({ + PrevRank, + NewRank, + Progress: journalEntry.Progress, + InventoryChanges, + AffiliationMods + }); + break; + } + + default: + logger.debug(`data provided to ${req.path}: ${String(req.body)}`); + throw new Error(`unknown researchMushroom mode: ${payload.Mode}`); + } +}; + +interface IResearchMushroom { + Mode: string; // r + MushroomItem: string; + Amount: number; + Convert: boolean; +} + +const journalEntriesRank: Record = { + "/Lotus/Types/Items/MushroomJournal/PlainMushroomJournalItem": 1, + "/Lotus/Types/Items/MushroomJournal/GasMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/ToxinMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/ViralMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/MagneticMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/ElectricMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/TauMushroomJournalItem": 5, + "/Lotus/Types/Items/MushroomJournal/SlashMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/BlastMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/ImpactMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/ColdMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/CorrosiveMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/PunctureMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/HeatMushroomJournalItem": 3, + "/Lotus/Types/Items/MushroomJournal/RadiationMushroomJournalItem": 4, + "/Lotus/Types/Items/MushroomJournal/VoidMushroomJournalItem": 5 +}; + +const syndicateTitleThresholds = [0, 1, 2, 6, 12, 16]; + +const getJournalRank = (journalEntry: IJournalEntry): number => { + const k = journalEntriesRank[journalEntry.EntryType]; + if (!k) return 0; + + const thresholds = [k * 1, k * 3, k * 6]; + + if (journalEntry.Progress >= thresholds[2]) return 3; + if (journalEntry.Progress >= thresholds[1]) return 2; + if (journalEntry.Progress >= thresholds[0]) return 1; + return 0; +}; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index dcb92b89..909639cc 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -88,7 +88,9 @@ import type { IGoalProgressDatabase, IGoalProgressClient, IKubrowPetPrintClient, - IKubrowPetPrintDatabase + IKubrowPetPrintDatabase, + INokkoColony, + IJournalEntry } from "../../types/inventoryTypes/inventoryTypes.ts"; import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { IOid, ITypeCount } from "../../types/commonTypes.ts"; @@ -1424,6 +1426,22 @@ const hubNpcCustomizationSchema = new Schema( { _id: false } ); +const journalEntrySchema = new Schema( + { + EntryType: String, + Progress: Number + }, + { _id: false } +); + +const nokkoColonySchema = new Schema( + { + FeedLevel: Number, + JournalEntries: [journalEntrySchema] + }, + { _id: false } +); + const inventorySchema = new Schema( { accountOwnerId: Schema.Types.ObjectId, @@ -1840,7 +1858,9 @@ const inventorySchema = new Schema( ClaimedJunctionChallengeRewards: { type: [String], default: undefined }, - SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined } + SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined }, + + NokkoColony: { type: nokkoColonySchema, default: undefined } }, { timestamps: { createdAt: "Created", updatedAt: false } } ); diff --git a/src/routes/api.ts b/src/routes/api.ts index 59bbc83b..5502a0c8 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -50,6 +50,7 @@ import { dronesController } from "../controllers/api/dronesController.ts"; import { endlessXpController } from "../controllers/api/endlessXpController.ts"; import { entratiLabConquestModeController } from "../controllers/api/entratiLabConquestModeController.ts"; import { evolveWeaponController } from "../controllers/api/evolveWeaponController.ts"; +import { feedPrinceController } from "../controllers/api/feedPrinceController.ts"; import { findSessionsController } from "../controllers/api/findSessionsController.ts"; import { fishmongerController } from "../controllers/api/fishmongerController.ts"; import { focusController } from "../controllers/api/focusController.ts"; @@ -116,6 +117,7 @@ import { removeFromGuildController } from "../controllers/api/removeFromGuildCon import { removeIgnoredUserController } from "../controllers/api/removeIgnoredUserController.ts"; import { renamePetController } from "../controllers/api/renamePetController.ts"; import { rerollRandomModController } from "../controllers/api/rerollRandomModController.ts"; +import { researchMushroomController } from "../controllers/api/researchMushroomController.ts"; import { resetQuestProgressController } from "../controllers/api/resetQuestProgressController.ts"; import { retrievePetFromStasisController } from "../controllers/api/retrievePetFromStasisController.ts"; import { saveDialogueController } from "../controllers/api/saveDialogueController.ts"; @@ -271,6 +273,7 @@ apiRouter.post("/drones.php", dronesController); apiRouter.post("/endlessXp.php", endlessXpController); apiRouter.post("/entratiLabConquestMode.php", entratiLabConquestModeController); apiRouter.post("/evolveWeapon.php", evolveWeaponController); +apiRouter.post("/feedPrince.php", feedPrinceController); apiRouter.post("/findSessions.php", findSessionsController); apiRouter.post("/fishmonger.php", fishmongerController); apiRouter.post("/focus.php", focusController); @@ -318,6 +321,7 @@ apiRouter.post("/removeFromGuild.php", removeFromGuildController); apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController); apiRouter.post("/renamePet.php", renamePetController); apiRouter.post("/rerollRandomMod.php", rerollRandomModController); +apiRouter.post("/researchMushroom.php", researchMushroomController); apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController); apiRouter.post("/saveDialogue.php", saveDialogueController); apiRouter.post("/saveLoadout.php", saveLoadoutController); diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index d4c96e66..cc44c061 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -436,6 +436,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu Ship?: IOrbiterClient; // U22 and below, response only ClaimedJunctionChallengeRewards?: string[]; // U39 SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus + NokkoColony?: INokkoColony; // Field Guide } export interface IAffiliation { @@ -1220,3 +1221,13 @@ export interface IHubNpcCustomization { Pattern: string; Tag: string; } + +export interface IJournalEntry { + EntryType: string; + Progress: number; +} + +export interface INokkoColony { + FeedLevel: number; + JournalEntries: IJournalEntry[]; +}