chore: more faithful handling of daily tribute #1324

Merged
Sainan merged 1 commits from daily-tribute-ii into main 2025-03-25 15:11:27 -07:00
4 changed files with 37 additions and 14 deletions

View File

@ -1,31 +1,42 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountForRequest } from "@/src/services/loginService"; import { getAccountForRequest } from "@/src/services/loginService";
import { claimLoginReward, getRandomLoginRewards, ILoginRewardsReponse } from "@/src/services/loginRewardService"; import {
claimLoginReward,
getRandomLoginRewards,
ILoginRewardsReponse,
isLoginRewardAChoice
} from "@/src/services/loginRewardService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
export const loginRewardsController: RequestHandler = async (req, res) => { export const loginRewardsController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
const today = Math.trunc(Date.now() / 86400000) * 86400; const today = Math.trunc(Date.now() / 86400000) * 86400;
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
if (today == account.LastLoginRewardDate) { if (today == account.LastLoginRewardDate) {
res.end(); res.json({
DailyTributeInfo: {
IsMilestoneDay: isMilestoneDay,
IsChooseRewardSet: isLoginRewardAChoice(account),
LoginDays: account.LoginDays,
NextMilestoneReward: "",
NextMilestoneDay: nextMilestoneDay
}
} satisfies ILoginRewardsReponse);
return; return;
} }
account.LoginDays += 1;
account.LastLoginRewardDate = today;
await account.save();
const inventory = await getInventory(account._id.toString()); const inventory = await getInventory(account._id.toString());
const randomRewards = getRandomLoginRewards(account, inventory); const randomRewards = getRandomLoginRewards(account, inventory);
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
const response: ILoginRewardsReponse = { const response: ILoginRewardsReponse = {
DailyTributeInfo: { DailyTributeInfo: {
Rewards: randomRewards, Rewards: randomRewards,
IsMilestoneDay: isMilestoneDay, IsMilestoneDay: isMilestoneDay,
IsChooseRewardSet: randomRewards.length != 1, IsChooseRewardSet: randomRewards.length != 1,
LoginDays: account.LoginDays, LoginDays: account.LoginDays,
//NextMilestoneReward: "", NextMilestoneReward: "",
NextMilestoneDay: account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50, NextMilestoneDay: nextMilestoneDay,
HasChosenReward: false HasChosenReward: false
}, },
LastLoginRewardDate: today LastLoginRewardDate: today
@ -33,7 +44,7 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
if (!isMilestoneDay && randomRewards.length == 1) { if (!isMilestoneDay && randomRewards.length == 1) {
response.DailyTributeInfo.HasChosenReward = true; response.DailyTributeInfo.HasChosenReward = true;
response.DailyTributeInfo.ChosenReward = randomRewards[0]; response.DailyTributeInfo.ChosenReward = randomRewards[0];
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]); response.DailyTributeInfo.NewInventory = await claimLoginReward(account, inventory, randomRewards[0]);
await inventory.save(); await inventory.save();
} }
res.json(response); res.json(response);

View File

@ -28,7 +28,7 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
} else { } else {
const randomRewards = getRandomLoginRewards(account, inventory); const randomRewards = getRandomLoginRewards(account, inventory);
chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!; chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
inventoryChanges = await claimLoginReward(inventory, chosenReward); inventoryChanges = await claimLoginReward(account, inventory, chosenReward);
} }
await inventory.save(); await inventory.save();
res.json({ res.json({

View File

@ -23,7 +23,7 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
Dropped: Boolean, Dropped: Boolean,
LatestEventMessageDate: { type: Date, default: 0 }, LatestEventMessageDate: { type: Date, default: 0 },
LastLoginRewardDate: { type: Number, default: 0 }, LastLoginRewardDate: { type: Number, default: 0 },
LoginDays: { type: Number, default: 0 } LoginDays: { type: Number, default: 1 }
}, },
opts opts
); );

View File

@ -14,7 +14,7 @@ export interface ILoginRewardsReponse {
IsMilestoneDay?: boolean; IsMilestoneDay?: boolean;
IsChooseRewardSet?: boolean; IsChooseRewardSet?: boolean;
LoginDays?: number; // when calling multiple times per day, this is already incremented to represent "tomorrow" LoginDays?: number; // when calling multiple times per day, this is already incremented to represent "tomorrow"
//NextMilestoneReward?: ""; NextMilestoneReward?: "";
NextMilestoneDay?: number; // seems to not be used if IsMilestoneDay NextMilestoneDay?: number; // seems to not be used if IsMilestoneDay
HasChosenReward?: boolean; HasChosenReward?: boolean;
NewInventory?: IInventoryChanges; NewInventory?: IInventoryChanges;
@ -46,6 +46,13 @@ const scaleAmount = (day: number, amount: number, scalingMultiplier: number): nu
return amount + Math.min(day, 3000) / divisor; return amount + Math.min(day, 3000) / divisor;
}; };
// Always produces the same result for the same account _id & LoginDays pair.
export const isLoginRewardAChoice = (account: TAccountDocument): boolean => {
const accountSeed = parseInt(account._id.toString().substring(16), 16);
const rng = new CRng(mixSeeds(accountSeed, account.LoginDays));
return rng.random() < 0.25; // Using 25% as an approximate chance for pick-a-doors. More conclusive data analysis is needed.
};
// Always produces the same result for the same account _id & LoginDays pair. // Always produces the same result for the same account _id & LoginDays pair.
export const getRandomLoginRewards = ( export const getRandomLoginRewards = (
account: TAccountDocument, account: TAccountDocument,
@ -53,9 +60,9 @@ export const getRandomLoginRewards = (
): ILoginReward[] => { ): ILoginReward[] => {
const accountSeed = parseInt(account._id.toString().substring(16), 16); const accountSeed = parseInt(account._id.toString().substring(16), 16);
const rng = new CRng(mixSeeds(accountSeed, account.LoginDays)); const rng = new CRng(mixSeeds(accountSeed, account.LoginDays));
const pick_a_door = rng.random() < 0.25; // Using 25% as an approximate chance for pick-a-doors. More conclusive data analysis is needed.
const rewards = [getRandomLoginReward(rng, account.LoginDays, inventory)]; const rewards = [getRandomLoginReward(rng, account.LoginDays, inventory)];
// Using 25% an approximate chance for pick-a-doors. More conclusive data analysis is needed. if (pick_a_door) {
if (rng.random() < 0.25) {
do { do {
const reward = getRandomLoginReward(rng, account.LoginDays, inventory); const reward = getRandomLoginReward(rng, account.LoginDays, inventory);
if (!rewards.find(x => x.StoreItemType == reward.StoreItemType)) { if (!rewards.find(x => x.StoreItemType == reward.StoreItemType)) {
@ -114,9 +121,14 @@ const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatab
}; };
export const claimLoginReward = async ( export const claimLoginReward = async (
account: TAccountDocument,
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
reward: ILoginReward reward: ILoginReward
): Promise<IInventoryChanges> => { ): Promise<IInventoryChanges> => {
account.LoginDays += 1;
account.LastLoginRewardDate = Math.trunc(Date.now() / 86400000) * 86400;
await account.save();
switch (reward.RewardType) { switch (reward.RewardType) {
case "RT_RESOURCE": case "RT_RESOURCE":
case "RT_STORE_ITEM": case "RT_STORE_ITEM":