From 4895b4630b930478ebde136b3bb04a9e3c60a0b1 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 08:20:37 -0700 Subject: [PATCH] feat: credit boosters (+ daily first win) (#2324) Daily first win is kinda weird because the client doesn't even seem to acknowledge it. Also fixed missionCompletionCredits being added to inventory inconsistently (sometimes once, sometimes twice). Closes #1086 Closes #2322 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2324 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/checkDailyMissionBonusController.ts | 22 +++---- .../api/missionInventoryUpdateController.ts | 2 +- .../custom/completeAllMissionsController.ts | 2 +- .../custom/setBoosterController.ts | 4 +- src/models/loginModel.ts | 3 +- src/services/missionInventoryUpdateService.ts | 65 ++++++++++++------- src/services/questService.ts | 2 +- src/types/loginTypes.ts | 1 + src/types/missionTypes.ts | 6 +- static/webui/script.js | 3 +- 10 files changed, 63 insertions(+), 47 deletions(-) diff --git a/src/controllers/api/checkDailyMissionBonusController.ts b/src/controllers/api/checkDailyMissionBonusController.ts index 97b838fe..8e457142 100644 --- a/src/controllers/api/checkDailyMissionBonusController.ts +++ b/src/controllers/api/checkDailyMissionBonusController.ts @@ -1,16 +1,12 @@ +import { getAccountForRequest } from "@/src/services/loginService"; import { RequestHandler } from "express"; -const checkDailyMissionBonusController: RequestHandler = (_req, res) => { - const data = Buffer.from([ - 0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a, - 0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, - 0x3a, 0x31, 0x0a - ]); - res.writeHead(200, { - "Content-Type": "text/html", - "Content-Length": data.length - }); - res.end(data); +export const checkDailyMissionBonusController: RequestHandler = async (req, res) => { + const account = await getAccountForRequest(req); + const today = Math.trunc(Date.now() / 86400000) * 86400; + if (account.DailyFirstWinDate != today) { + res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n"); + } else { + res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n"); + } }; - -export { checkDailyMissionBonusController }; diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index 93f8033c..e3b86b71 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -88,7 +88,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) AffiliationMods, SyndicateXPItemReward, ConquestCompletedMissionsCount - } = await addMissionRewards(inventory, missionReport, firstCompletion); + } = await addMissionRewards(account, inventory, missionReport, firstCompletion); if (missionReport.EndOfMatchUpload) { inventory.RewardSeed = generateRewardSeed(); diff --git a/src/controllers/custom/completeAllMissionsController.ts b/src/controllers/custom/completeAllMissionsController.ts index f6ab486d..66ac3c13 100644 --- a/src/controllers/custom/completeAllMissionsController.ts +++ b/src/controllers/custom/completeAllMissionsController.ts @@ -26,7 +26,7 @@ export const completeAllMissionsController: RequestHandler = async (req, res) => if (mission.Completes == 0) { mission.Completes++; if (node.missionReward) { - addFixedLevelRewards(node.missionReward, inventory, MissionRewards); + addFixedLevelRewards(node.missionReward, MissionRewards); } } mission.Tier = 1; diff --git a/src/controllers/custom/setBoosterController.ts b/src/controllers/custom/setBoosterController.ts index 28939614..b0a1ddbf 100644 --- a/src/controllers/custom/setBoosterController.ts +++ b/src/controllers/custom/setBoosterController.ts @@ -23,9 +23,9 @@ export const setBoosterController: RequestHandler = async (req, res) => { res.status(400).send("Invalid ItemType provided."); return; } - const now = Math.floor(Date.now() / 1000); + const now = Math.trunc(Date.now() / 1000); for (const { ItemType, ExpiryDate } of requests) { - if (ExpiryDate < now) { + if (ExpiryDate <= now) { // remove expired boosters const index = boosters.findIndex(item => item.ItemType === ItemType); if (index !== -1) { diff --git a/src/models/loginModel.ts b/src/models/loginModel.ts index 44dab113..aea5b993 100644 --- a/src/models/loginModel.ts +++ b/src/models/loginModel.ts @@ -25,7 +25,8 @@ const databaseAccountSchema = new Schema( LastLogin: { type: Date, default: 0 }, LatestEventMessageDate: { type: Date, default: 0 }, LastLoginRewardDate: { type: Number, default: 0 }, - LoginDays: { type: Number, default: 1 } + LoginDays: { type: Number, default: 1 }, + DailyFirstWinDate: { type: Number, default: 0 } }, opts ); diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 09bdc1c6..af8a2e40 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -962,6 +962,7 @@ const droptableAliases: Record = { //TODO: return type of partial missioninventoryupdate response export const addMissionRewards = async ( + account: TAccountDocument, inventory: TInventoryDatabaseDocument, { wagerTier: wagerTier, @@ -1009,13 +1010,17 @@ export const addMissionRewards = async ( const fixedLevelRewards = getLevelKeyRewards(levelKeyName); //logger.debug(`fixedLevelRewards ${fixedLevelRewards}`); if (fixedLevelRewards.levelKeyRewards) { - addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards, rewardInfo); + missionCompletionCredits += addFixedLevelRewards( + fixedLevelRewards.levelKeyRewards, + MissionRewards, + rewardInfo + ); } if (fixedLevelRewards.levelKeyRewards2) { for (const reward of fixedLevelRewards.levelKeyRewards2) { //quest stage completion credit rewards if (reward.rewardType == "RT_CREDITS") { - missionCompletionCredits += reward.amount; // will be added to inventory in addCredits + missionCompletionCredits += reward.amount; continue; } MissionRewards.push({ @@ -1044,12 +1049,11 @@ export const addMissionRewards = async ( ) { const levelCreditReward = getLevelCreditRewards(node); missionCompletionCredits += levelCreditReward; - inventory.RegularCredits += levelCreditReward; logger.debug(`levelCreditReward ${levelCreditReward}`); } if (node.missionReward) { - missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo); + missionCompletionCredits += addFixedLevelRewards(node.missionReward, MissionRewards, rewardInfo); } if (rewardInfo.sortieTag == "Mission1") { @@ -1159,7 +1163,9 @@ export const addMissionRewards = async ( combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges); } - const credits = addCredits(inventory, { + inventory.RegularCredits += missionCompletionCredits; + + const credits = await addCredits(account, inventory, { missionCompletionCredits, missionDropCredits: creditDrops ?? 0, rngRewardCredits: inventoryChanges.RegularCredits ?? 0 @@ -1382,48 +1388,61 @@ export const addMissionRewards = async ( }; }; -//creditBonus is not entirely accurate. -//TODO: consider ActiveBoosters -export const addCredits = ( +export const addCredits = async ( + account: TAccountDocument, inventory: TInventoryDatabaseDocument, { missionDropCredits, missionCompletionCredits, rngRewardCredits }: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number } -): IMissionCredits => { - const hasDailyCreditBonus = true; - const totalCredits = missionDropCredits + missionCompletionCredits + rngRewardCredits; - +): Promise => { const finalCredits: IMissionCredits = { MissionCredits: [missionDropCredits, missionDropCredits], - CreditBonus: [missionCompletionCredits, missionCompletionCredits], - TotalCredits: [totalCredits, totalCredits] + CreditsBonus: [missionCompletionCredits, missionCompletionCredits], + TotalCredits: [0, 0] }; - if (hasDailyCreditBonus) { + const today = Math.trunc(Date.now() / 86400000) * 86400; + if (account.DailyFirstWinDate != today) { + account.DailyFirstWinDate = today; + await account.save(); + + logger.debug(`daily first win, doubling missionCompletionCredits (${missionCompletionCredits})`); + + finalCredits.DailyMissionBonus = true; inventory.RegularCredits += missionCompletionCredits; - finalCredits.CreditBonus[1] *= 2; - finalCredits.MissionCredits[1] *= 2; - finalCredits.TotalCredits[1] *= 2; + finalCredits.CreditsBonus[1] *= 2; } - if (!hasDailyCreditBonus) { - return finalCredits; + const totalCredits = finalCredits.MissionCredits[1] + finalCredits.CreditsBonus[1] + rngRewardCredits; + finalCredits.TotalCredits = [totalCredits, totalCredits]; + + if (config.worldState?.creditBoost) { + inventory.RegularCredits += finalCredits.TotalCredits[1]; + finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; } - return { ...finalCredits, DailyMissionBonus: true }; + const now = Math.trunc(Date.now() / 1000); // TOVERIFY: Should we maybe subtract mission time as to apply credit boosters that expired during mission? + if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBooster")?.ExpiryDate ?? 0) > now) { + inventory.RegularCredits += finalCredits.TotalCredits[1]; + finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; + } + if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) { + inventory.RegularCredits += finalCredits.TotalCredits[1]; + finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; + } + + return finalCredits; }; export const addFixedLevelRewards = ( rewards: IMissionRewardExternal, - inventory: TInventoryDatabaseDocument, MissionRewards: IMissionReward[], rewardInfo?: IRewardInfo ): number => { let missionBonusCredits = 0; if (rewards.credits) { missionBonusCredits += rewards.credits; - inventory.RegularCredits += rewards.credits; } if (rewards.items) { for (const item of rewards.items) { diff --git a/src/services/questService.ts b/src/services/questService.ts index 053f6eb4..633f1022 100644 --- a/src/services/questService.ts +++ b/src/services/questService.ts @@ -331,7 +331,7 @@ export const giveKeyChainMissionReward = async ( const fixedLevelRewards = getLevelKeyRewards(missionName); if (fixedLevelRewards.levelKeyRewards) { const missionRewards: { StoreItem: string; ItemCount: number }[] = []; - addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards); + inventory.RegularCredits += addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, missionRewards); for (const reward of missionRewards) { await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount); diff --git a/src/types/loginTypes.ts b/src/types/loginTypes.ts index ef280188..0f5cea60 100644 --- a/src/types/loginTypes.ts +++ b/src/types/loginTypes.ts @@ -25,6 +25,7 @@ export interface IDatabaseAccount extends IDatabaseAccountRequiredFields { LatestEventMessageDate: Date; LastLoginRewardDate: number; LoginDays: number; + DailyFirstWinDate: number; } // Includes virtual ID diff --git a/src/types/missionTypes.ts b/src/types/missionTypes.ts index 3de75318..bb70fc30 100644 --- a/src/types/missionTypes.ts +++ b/src/types/missionTypes.ts @@ -17,9 +17,9 @@ export interface IMissionReward { } export interface IMissionCredits { - MissionCredits: number[]; - CreditBonus: number[]; - TotalCredits: number[]; + MissionCredits: [number, number]; + CreditsBonus: [number, number]; // "Credit Reward"; `CreditsBonus[1]` is `CreditsBonus[0] * 2` if DailyMissionBonus + TotalCredits: [number, number]; DailyMissionBonus?: boolean; } diff --git a/static/webui/script.js b/static/webui/script.js index 479f0014..e90f0ed0 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -2289,14 +2289,13 @@ function doAcquireBoosters() { const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days setBooster(uniqueName, ExpiryDate, () => { $("#acquire-type-Boosters").val(""); - updateInventory(); }); } function doChangeBoosterExpiry(ItemType, ExpiryDateInput) { console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value); // cast local datetime string to unix timestamp - const ExpiryDate = new Date(ExpiryDateInput.value).getTime() / 1000; + const ExpiryDate = Math.trunc(new Date(ExpiryDateInput.value).getTime() / 1000); if (isNaN(ExpiryDate)) { ExpiryDateInput.addClass("is-invalid").focus(); return false;