feat: daily tribute (#1241)
Closes #367 Reviewed-on: OpenWF/SpaceNinjaServer#1241
This commit is contained in:
		
							parent
							
								
									e83970d326
								
							
						
					
					
						commit
						6598318fc5
					
				@ -42,8 +42,7 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
				
			|||||||
                ForceLogoutVersion: 0,
 | 
					                ForceLogoutVersion: 0,
 | 
				
			||||||
                ConsentNeeded: false,
 | 
					                ConsentNeeded: false,
 | 
				
			||||||
                TrackedSettings: [],
 | 
					                TrackedSettings: [],
 | 
				
			||||||
                Nonce: nonce,
 | 
					                Nonce: nonce
 | 
				
			||||||
                LatestEventMessageDate: new Date(0)
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            logger.debug("created new account");
 | 
					            logger.debug("created new account");
 | 
				
			||||||
            response.json(createLoginResponse(myAddress, newAccount, buildLabel));
 | 
					            response.json(createLoginResponse(myAddress, newAccount, buildLabel));
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,40 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import loginRewards from "@/static/fixed_responses/loginRewards.json";
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { claimLoginReward, getRandomLoginRewards, ILoginRewardsReponse } from "@/src/services/loginRewardService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const loginRewardsController: RequestHandler = (_req, res) => {
 | 
					export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    res.json(loginRewards);
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const today = Math.trunc(Date.now() / 86400000) * 86400;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (today == account.LastLoginRewardDate) {
 | 
				
			||||||
 | 
					        res.end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    account.LoginDays += 1;
 | 
				
			||||||
 | 
					    account.LastLoginRewardDate = today;
 | 
				
			||||||
 | 
					    await account.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventory = await getInventory(account._id.toString());
 | 
				
			||||||
 | 
					    const randomRewards = getRandomLoginRewards(account, inventory);
 | 
				
			||||||
 | 
					    const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
 | 
				
			||||||
 | 
					    const response: ILoginRewardsReponse = {
 | 
				
			||||||
 | 
					        DailyTributeInfo: {
 | 
				
			||||||
 | 
					            Rewards: randomRewards,
 | 
				
			||||||
 | 
					            IsMilestoneDay: isMilestoneDay,
 | 
				
			||||||
 | 
					            IsChooseRewardSet: randomRewards.length != 1,
 | 
				
			||||||
 | 
					            LoginDays: account.LoginDays,
 | 
				
			||||||
 | 
					            //NextMilestoneReward: "",
 | 
				
			||||||
 | 
					            NextMilestoneDay: account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50,
 | 
				
			||||||
 | 
					            HasChosenReward: false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        LastLoginRewardDate: today
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (!isMilestoneDay && randomRewards.length == 1) {
 | 
				
			||||||
 | 
					        response.DailyTributeInfo.HasChosenReward = true;
 | 
				
			||||||
 | 
					        response.DailyTributeInfo.ChosenReward = randomRewards[0];
 | 
				
			||||||
 | 
					        response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.json(response);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { loginRewardsController };
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										57
									
								
								src/controllers/api/loginRewardsSelectionController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/controllers/api/loginRewardsSelectionController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { claimLoginReward, getRandomLoginRewards } from "@/src/services/loginRewardService";
 | 
				
			||||||
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const loginRewardsSelectionController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(account._id.toString());
 | 
				
			||||||
 | 
					    const body = JSON.parse(String(req.body)) as ILoginRewardsSelectionRequest;
 | 
				
			||||||
 | 
					    const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
 | 
				
			||||||
 | 
					    if (body.IsMilestoneReward != isMilestoneDay) {
 | 
				
			||||||
 | 
					        logger.warn(`Client disagrees on login milestone (got ${body.IsMilestoneReward}, expected ${isMilestoneDay})`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let chosenReward;
 | 
				
			||||||
 | 
					    let inventoryChanges: IInventoryChanges;
 | 
				
			||||||
 | 
					    if (body.IsMilestoneReward) {
 | 
				
			||||||
 | 
					        chosenReward = {
 | 
				
			||||||
 | 
					            RewardType: "RT_STORE_ITEM",
 | 
				
			||||||
 | 
					            StoreItemType: body.ChosenReward
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        inventoryChanges = (await handleStoreItemAcquisition(body.ChosenReward, inventory)).InventoryChanges;
 | 
				
			||||||
 | 
					        if (!evergreenRewards.find(x => x == body.ChosenReward)) {
 | 
				
			||||||
 | 
					            inventory.LoginMilestoneRewards.push(body.ChosenReward);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const randomRewards = getRandomLoginRewards(account, inventory);
 | 
				
			||||||
 | 
					        chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
 | 
				
			||||||
 | 
					        inventoryChanges = await claimLoginReward(inventory, chosenReward);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        DailyTributeInfo: {
 | 
				
			||||||
 | 
					            NewInventory: inventoryChanges,
 | 
				
			||||||
 | 
					            ChosenReward: chosenReward
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ILoginRewardsSelectionRequest {
 | 
				
			||||||
 | 
					    ChosenReward: string;
 | 
				
			||||||
 | 
					    IsMilestoneReward: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const evergreenRewards = [
 | 
				
			||||||
 | 
					    "/Lotus/Types/StoreItems/Packages/EvergreenTripleForma",
 | 
				
			||||||
 | 
					    "/Lotus/Types/StoreItems/Packages/EvergreenTripleRifleRiven",
 | 
				
			||||||
 | 
					    "/Lotus/Types/StoreItems/Packages/EvergreenTripleMeleeRiven",
 | 
				
			||||||
 | 
					    "/Lotus/Types/StoreItems/Packages/EvergreenTripleSecondaryRiven",
 | 
				
			||||||
 | 
					    "/Lotus/Types/StoreItems/Packages/EvergreenWeaponSlots",
 | 
				
			||||||
 | 
					    "/Lotus/Types/StoreItems/Packages/EvergreenKuva",
 | 
				
			||||||
 | 
					    "/Lotus/Types/StoreItems/Packages/EvergreenBoosters",
 | 
				
			||||||
 | 
					    "/Lotus/Types/StoreItems/Packages/EvergreenEndo",
 | 
				
			||||||
 | 
					    "/Lotus/Types/StoreItems/Packages/EvergreenExilus"
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { IAccountCreation } from "@/src/types/customTypes";
 | 
					import { IAccountCreation } from "@/src/types/customTypes";
 | 
				
			||||||
import { IDatabaseAccount } from "@/src/types/loginTypes";
 | 
					import { IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
 | 
				
			||||||
import crypto from "crypto";
 | 
					import crypto from "crypto";
 | 
				
			||||||
import { isString, parseEmail, parseString } from "../general";
 | 
					import { isString, parseEmail, parseString } from "../general";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -40,7 +40,7 @@ const toAccountCreation = (accountCreation: unknown): IAccountCreation => {
 | 
				
			|||||||
    throw new Error("incorrect account creation data: incorrect properties");
 | 
					    throw new Error("incorrect account creation data: incorrect properties");
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const toDatabaseAccount = (createAccount: IAccountCreation): IDatabaseAccount => {
 | 
					const toDatabaseAccount = (createAccount: IAccountCreation): IDatabaseAccountRequiredFields => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        ...createAccount,
 | 
					        ...createAccount,
 | 
				
			||||||
        ClientType: "",
 | 
					        ClientType: "",
 | 
				
			||||||
@ -48,9 +48,8 @@ const toDatabaseAccount = (createAccount: IAccountCreation): IDatabaseAccount =>
 | 
				
			|||||||
        CrossPlatformAllowed: true,
 | 
					        CrossPlatformAllowed: true,
 | 
				
			||||||
        ForceLogoutVersion: 0,
 | 
					        ForceLogoutVersion: 0,
 | 
				
			||||||
        TrackedSettings: [],
 | 
					        TrackedSettings: [],
 | 
				
			||||||
        Nonce: 0,
 | 
					        Nonce: 0
 | 
				
			||||||
        LatestEventMessageDate: new Date(0)
 | 
					    } satisfies IDatabaseAccountRequiredFields;
 | 
				
			||||||
    } satisfies IDatabaseAccount;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { toDatabaseAccount, toAccountCreation as toCreateAccount };
 | 
					export { toDatabaseAccount, toAccountCreation as toCreateAccount };
 | 
				
			||||||
 | 
				
			|||||||
@ -1366,7 +1366,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
				
			|||||||
        ThemeSounds: String,
 | 
					        ThemeSounds: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //Daily LoginRewards
 | 
					        //Daily LoginRewards
 | 
				
			||||||
        LoginMilestoneRewards: [String],
 | 
					        LoginMilestoneRewards: { type: [String], default: [] },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //You first Dialog with NPC or use new Item
 | 
					        //You first Dialog with NPC or use new Item
 | 
				
			||||||
        NodeIntrosCompleted: [String],
 | 
					        NodeIntrosCompleted: [String],
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,9 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
 | 
				
			|||||||
        TrackedSettings: { type: [String], default: [] },
 | 
					        TrackedSettings: { type: [String], default: [] },
 | 
				
			||||||
        Nonce: { type: Number, default: 0 },
 | 
					        Nonce: { type: Number, default: 0 },
 | 
				
			||||||
        Dropped: Boolean,
 | 
					        Dropped: Boolean,
 | 
				
			||||||
        LatestEventMessageDate: { type: Date, default: 0 }
 | 
					        LatestEventMessageDate: { type: Date, default: 0 },
 | 
				
			||||||
 | 
					        LastLoginRewardDate: { type: Number, default: 0 },
 | 
				
			||||||
 | 
					        LoginDays: { type: Number, default: 0 }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    opts
 | 
					    opts
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
				
			|||||||
@ -63,6 +63,7 @@ import { inventorySlotsController } from "@/src/controllers/api/inventorySlotsCo
 | 
				
			|||||||
import { joinSessionController } from "@/src/controllers/api/joinSessionController";
 | 
					import { joinSessionController } from "@/src/controllers/api/joinSessionController";
 | 
				
			||||||
import { loginController } from "@/src/controllers/api/loginController";
 | 
					import { loginController } from "@/src/controllers/api/loginController";
 | 
				
			||||||
import { loginRewardsController } from "@/src/controllers/api/loginRewardsController";
 | 
					import { loginRewardsController } from "@/src/controllers/api/loginRewardsController";
 | 
				
			||||||
 | 
					import { loginRewardsSelectionController } from "@/src/controllers/api/loginRewardsSelectionController";
 | 
				
			||||||
import { logoutController } from "@/src/controllers/api/logoutController";
 | 
					import { logoutController } from "@/src/controllers/api/logoutController";
 | 
				
			||||||
import { marketRecommendationsController } from "@/src/controllers/api/marketRecommendationsController";
 | 
					import { marketRecommendationsController } from "@/src/controllers/api/marketRecommendationsController";
 | 
				
			||||||
import { missionInventoryUpdateController } from "@/src/controllers/api/missionInventoryUpdateController";
 | 
					import { missionInventoryUpdateController } from "@/src/controllers/api/missionInventoryUpdateController";
 | 
				
			||||||
@ -202,6 +203,7 @@ apiRouter.post("/infestedFoundry.php", infestedFoundryController);
 | 
				
			|||||||
apiRouter.post("/inventorySlots.php", inventorySlotsController);
 | 
					apiRouter.post("/inventorySlots.php", inventorySlotsController);
 | 
				
			||||||
apiRouter.post("/joinSession.php", joinSessionController);
 | 
					apiRouter.post("/joinSession.php", joinSessionController);
 | 
				
			||||||
apiRouter.post("/login.php", loginController);
 | 
					apiRouter.post("/login.php", loginController);
 | 
				
			||||||
 | 
					apiRouter.post("/loginRewardsSelection.php", loginRewardsSelectionController);
 | 
				
			||||||
apiRouter.post("/missionInventoryUpdate.php", missionInventoryUpdateController);
 | 
					apiRouter.post("/missionInventoryUpdate.php", missionInventoryUpdateController);
 | 
				
			||||||
apiRouter.post("/modularWeaponCrafting.php", modularWeaponCraftingController);
 | 
					apiRouter.post("/modularWeaponCrafting.php", modularWeaponCraftingController);
 | 
				
			||||||
apiRouter.post("/modularWeaponSale.php", modularWeaponSaleController);
 | 
					apiRouter.post("/modularWeaponSale.php", modularWeaponSaleController);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										140
									
								
								src/services/loginRewardService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/services/loginRewardService.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					import randomRewards from "@/static/fixed_responses/loginRewards/randomRewards.json";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "../types/purchaseTypes";
 | 
				
			||||||
 | 
					import { TAccountDocument } from "./loginService";
 | 
				
			||||||
 | 
					import { CRng, mixSeeds } from "./rngService";
 | 
				
			||||||
 | 
					import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					import { addBooster, updateCurrency } from "./inventoryService";
 | 
				
			||||||
 | 
					import { handleStoreItemAcquisition } from "./purchaseService";
 | 
				
			||||||
 | 
					import { ExportBoosters, ExportRecipes, ExportWarframes, ExportWeapons } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { toStoreItem } from "./itemDataService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ILoginRewardsReponse {
 | 
				
			||||||
 | 
					    DailyTributeInfo: {
 | 
				
			||||||
 | 
					        Rewards?: ILoginReward[]; // only set on first call of the day
 | 
				
			||||||
 | 
					        IsMilestoneDay?: boolean;
 | 
				
			||||||
 | 
					        IsChooseRewardSet?: boolean;
 | 
				
			||||||
 | 
					        LoginDays?: number; // when calling multiple times per day, this is already incremented to represent "tomorrow"
 | 
				
			||||||
 | 
					        //NextMilestoneReward?: "";
 | 
				
			||||||
 | 
					        NextMilestoneDay?: number; // seems to not be used if IsMilestoneDay
 | 
				
			||||||
 | 
					        HasChosenReward?: boolean;
 | 
				
			||||||
 | 
					        NewInventory?: IInventoryChanges;
 | 
				
			||||||
 | 
					        ChosenReward?: ILoginReward;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    LastLoginRewardDate?: number; // only set on first call of the day; today at 0 UTC
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ILoginReward {
 | 
				
			||||||
 | 
					    //_id: IOid;
 | 
				
			||||||
 | 
					    RewardType: string;
 | 
				
			||||||
 | 
					    //CouponType: "CPT_PLATINUM";
 | 
				
			||||||
 | 
					    Icon: string;
 | 
				
			||||||
 | 
					    //ItemType: "";
 | 
				
			||||||
 | 
					    StoreItemType: string; // uniquely identifies the reward
 | 
				
			||||||
 | 
					    //ProductCategory: "Pistols";
 | 
				
			||||||
 | 
					    Amount: number;
 | 
				
			||||||
 | 
					    ScalingMultiplier: number;
 | 
				
			||||||
 | 
					    //Durability: "COMMON";
 | 
				
			||||||
 | 
					    //DisplayName: "";
 | 
				
			||||||
 | 
					    Duration: number;
 | 
				
			||||||
 | 
					    //CouponSku: number;
 | 
				
			||||||
 | 
					    //Rarity: number;
 | 
				
			||||||
 | 
					    Transmission: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const scaleAmount = (day: number, amount: number, scalingMultiplier: number): number => {
 | 
				
			||||||
 | 
					    const divisor = 200 / (amount * scalingMultiplier);
 | 
				
			||||||
 | 
					    return amount + Math.min(day, 3000) / divisor;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Always produces the same result for the same account _id & LoginDays pair.
 | 
				
			||||||
 | 
					export const getRandomLoginRewards = (
 | 
				
			||||||
 | 
					    account: TAccountDocument,
 | 
				
			||||||
 | 
					    inventory: TInventoryDatabaseDocument
 | 
				
			||||||
 | 
					): ILoginReward[] => {
 | 
				
			||||||
 | 
					    const accountSeed = parseInt(account._id.toString().substring(16), 16);
 | 
				
			||||||
 | 
					    const rng = new CRng(mixSeeds(accountSeed, account.LoginDays));
 | 
				
			||||||
 | 
					    const rewards = [getRandomLoginReward(rng, account.LoginDays, inventory)];
 | 
				
			||||||
 | 
					    // Using 25% an approximate chance for pick-a-doors. More conclusive data analysis is needed.
 | 
				
			||||||
 | 
					    if (rng.random() < 0.25) {
 | 
				
			||||||
 | 
					        do {
 | 
				
			||||||
 | 
					            const reward = getRandomLoginReward(rng, account.LoginDays, inventory);
 | 
				
			||||||
 | 
					            if (!rewards.find(x => x.StoreItemType == reward.StoreItemType)) {
 | 
				
			||||||
 | 
					                rewards.push(reward);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } while (rewards.length != 3);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return rewards;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatabaseDocument): ILoginReward => {
 | 
				
			||||||
 | 
					    const reward = rng.randomReward(randomRewards)!;
 | 
				
			||||||
 | 
					    //const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!;
 | 
				
			||||||
 | 
					    if (reward.RewardType == "RT_RANDOM_RECIPE") {
 | 
				
			||||||
 | 
					        // Not very faithful implementation but roughly the same idea
 | 
				
			||||||
 | 
					        const masteredItems = new Set();
 | 
				
			||||||
 | 
					        for (const entry of inventory.XPInfo) {
 | 
				
			||||||
 | 
					            masteredItems.add(entry.ItemType);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const unmasteredItems = new Set();
 | 
				
			||||||
 | 
					        for (const uniqueName of Object.keys(ExportWeapons)) {
 | 
				
			||||||
 | 
					            if (!masteredItems.has(uniqueName)) {
 | 
				
			||||||
 | 
					                unmasteredItems.add(uniqueName);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const uniqueName of Object.keys(ExportWarframes)) {
 | 
				
			||||||
 | 
					            if (!masteredItems.has(uniqueName)) {
 | 
				
			||||||
 | 
					                unmasteredItems.add(uniqueName);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const eligibleRecipes: string[] = [];
 | 
				
			||||||
 | 
					        for (const [uniqueName, recipe] of Object.entries(ExportRecipes)) {
 | 
				
			||||||
 | 
					            if (unmasteredItems.has(recipe.resultType)) {
 | 
				
			||||||
 | 
					                eligibleRecipes.push(uniqueName);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        reward.StoreItemType = toStoreItem(rng.randomElement(eligibleRecipes));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        //_id: toOid(new Types.ObjectId()),
 | 
				
			||||||
 | 
					        RewardType: reward.RewardType,
 | 
				
			||||||
 | 
					        //CouponType: "CPT_PLATINUM",
 | 
				
			||||||
 | 
					        Icon: reward.Icon ?? "",
 | 
				
			||||||
 | 
					        //ItemType: "",
 | 
				
			||||||
 | 
					        StoreItemType: reward.StoreItemType,
 | 
				
			||||||
 | 
					        //ProductCategory: "Pistols",
 | 
				
			||||||
 | 
					        Amount: reward.Duration ? 1 : Math.round(scaleAmount(day, reward.Amount, reward.ScalingMultiplier)),
 | 
				
			||||||
 | 
					        ScalingMultiplier: reward.ScalingMultiplier,
 | 
				
			||||||
 | 
					        //Durability: "COMMON",
 | 
				
			||||||
 | 
					        //DisplayName: "",
 | 
				
			||||||
 | 
					        Duration: reward.Duration ? Math.round(reward.Duration * scaleAmount(day, 1, reward.ScalingMultiplier)) : 0,
 | 
				
			||||||
 | 
					        //CouponSku: 0,
 | 
				
			||||||
 | 
					        //Rarity: 0,
 | 
				
			||||||
 | 
					        Transmission: reward.Transmission
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const claimLoginReward = async (
 | 
				
			||||||
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
 | 
					    reward: ILoginReward
 | 
				
			||||||
 | 
					): Promise<IInventoryChanges> => {
 | 
				
			||||||
 | 
					    switch (reward.RewardType) {
 | 
				
			||||||
 | 
					        case "RT_RESOURCE":
 | 
				
			||||||
 | 
					        case "RT_STORE_ITEM":
 | 
				
			||||||
 | 
					        case "RT_RECIPE":
 | 
				
			||||||
 | 
					        case "RT_RANDOM_RECIPE":
 | 
				
			||||||
 | 
					            return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount)).InventoryChanges;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "RT_CREDITS":
 | 
				
			||||||
 | 
					            return updateCurrency(inventory, -reward.Amount, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "RT_BOOSTER": {
 | 
				
			||||||
 | 
					            const ItemType = ExportBoosters[reward.StoreItemType].typeName;
 | 
				
			||||||
 | 
					            const ExpiryDate = 3600 * reward.Duration;
 | 
				
			||||||
 | 
					            addBooster(ItemType, ExpiryDate, inventory);
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                Boosters: [{ ItemType, ExpiryDate }]
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    throw new Error(`unknown login reward type: ${reward.RewardType}`);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Account } from "@/src/models/loginModel";
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
import { createInventory } from "@/src/services/inventoryService";
 | 
					import { createInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { IDatabaseAccount, IDatabaseAccountJson } from "@/src/types/loginTypes";
 | 
					import { IDatabaseAccountJson, IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
 | 
				
			||||||
import { createShip } from "./shipService";
 | 
					import { createShip } from "./shipService";
 | 
				
			||||||
import { Document, Types } from "mongoose";
 | 
					import { Document, Types } from "mongoose";
 | 
				
			||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
					import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
				
			||||||
@ -18,7 +18,7 @@ export const isNameTaken = async (name: string): Promise<boolean> => {
 | 
				
			|||||||
    return !!(await Account.findOne({ DisplayName: name }));
 | 
					    return !!(await Account.findOne({ DisplayName: name }));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createAccount = async (accountData: IDatabaseAccount): Promise<IDatabaseAccountJson> => {
 | 
					export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise<IDatabaseAccountJson> => {
 | 
				
			||||||
    const account = new Account(accountData);
 | 
					    const account = new Account(accountData);
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        await account.save();
 | 
					        await account.save();
 | 
				
			||||||
@ -62,7 +62,7 @@ export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Typ
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line @typescript-eslint/ban-types
 | 
					// eslint-disable-next-line @typescript-eslint/ban-types
 | 
				
			||||||
type TAccountDocument = Document<unknown, {}, IDatabaseAccountJson> &
 | 
					export type TAccountDocument = Document<unknown, {}, IDatabaseAccountJson> &
 | 
				
			||||||
    IDatabaseAccountJson & { _id: Types.ObjectId; __v: number };
 | 
					    IDatabaseAccountJson & { _id: Types.ObjectId; __v: number };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getAccountForRequest = async (req: Request): Promise<TAccountDocument> => {
 | 
					export const getAccountForRequest = async (req: Request): Promise<TAccountDocument> => {
 | 
				
			||||||
 | 
				
			|||||||
@ -18,11 +18,11 @@ export const getRandomInt = (min: number, max: number): number => {
 | 
				
			|||||||
    return Math.floor(Math.random() * (max - min + 1)) + min;
 | 
					    return Math.floor(Math.random() * (max - min + 1)) + min;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getRandomReward = <T extends { probability: number }>(pool: T[]): T | undefined => {
 | 
					const getRewardAtPercentage = <T extends { probability: number }>(pool: T[], percentage: number): T | undefined => {
 | 
				
			||||||
    if (pool.length == 0) return;
 | 
					    if (pool.length == 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const totalChance = pool.reduce((accum, item) => accum + item.probability, 0);
 | 
					    const totalChance = pool.reduce((accum, item) => accum + item.probability, 0);
 | 
				
			||||||
    const randomValue = Math.random() * totalChance;
 | 
					    const randomValue = percentage * totalChance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let cumulativeChance = 0;
 | 
					    let cumulativeChance = 0;
 | 
				
			||||||
    for (const item of pool) {
 | 
					    for (const item of pool) {
 | 
				
			||||||
@ -34,6 +34,10 @@ export const getRandomReward = <T extends { probability: number }>(pool: T[]): T
 | 
				
			|||||||
    throw new Error("What the fuck?");
 | 
					    throw new Error("What the fuck?");
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getRandomReward = <T extends { probability: number }>(pool: T[]): T | undefined => {
 | 
				
			||||||
 | 
					    return getRewardAtPercentage(pool, Math.random());
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getRandomWeightedReward = <T extends { rarity: TRarity }>(
 | 
					export const getRandomWeightedReward = <T extends { rarity: TRarity }>(
 | 
				
			||||||
    pool: T[],
 | 
					    pool: T[],
 | 
				
			||||||
    weights: Record<TRarity, number>
 | 
					    weights: Record<TRarity, number>
 | 
				
			||||||
@ -70,6 +74,15 @@ export const getRandomWeightedRewardUc = <T extends { Rarity: TRarity }>(
 | 
				
			|||||||
    return getRandomReward(resultPool);
 | 
					    return getRandomReward(resultPool);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ChatGPT generated this. It seems to have a good enough distribution.
 | 
				
			||||||
 | 
					export const mixSeeds = (seed1: number, seed2: number): number => {
 | 
				
			||||||
 | 
					    let seed = seed1 ^ seed2;
 | 
				
			||||||
 | 
					    seed ^= seed >>> 21;
 | 
				
			||||||
 | 
					    seed ^= seed << 35;
 | 
				
			||||||
 | 
					    seed ^= seed >>> 4;
 | 
				
			||||||
 | 
					    return seed >>> 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Seeded RNG for internal usage. Based on recommendations in the ISO C standards.
 | 
					// Seeded RNG for internal usage. Based on recommendations in the ISO C standards.
 | 
				
			||||||
export class CRng {
 | 
					export class CRng {
 | 
				
			||||||
    state: number;
 | 
					    state: number;
 | 
				
			||||||
@ -92,6 +105,10 @@ export class CRng {
 | 
				
			|||||||
    randomElement<T>(arr: T[]): T {
 | 
					    randomElement<T>(arr: T[]): T {
 | 
				
			||||||
        return arr[Math.floor(this.random() * arr.length)];
 | 
					        return arr[Math.floor(this.random() * arr.length)];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
 | 
				
			||||||
 | 
					        return getRewardAtPercentage(pool, this.random());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Seeded RNG for cases where we need identical results to the game client. Based on work by Donald Knuth.
 | 
					// Seeded RNG for cases where we need identical results to the game client. Based on work by Donald Knuth.
 | 
				
			||||||
 | 
				
			|||||||
@ -11,11 +11,16 @@ export interface IAccountAndLoginResponseCommons {
 | 
				
			|||||||
    Nonce: number;
 | 
					    Nonce: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IDatabaseAccount extends IAccountAndLoginResponseCommons {
 | 
					export interface IDatabaseAccountRequiredFields extends IAccountAndLoginResponseCommons {
 | 
				
			||||||
    email: string;
 | 
					    email: string;
 | 
				
			||||||
    password: string;
 | 
					    password: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IDatabaseAccount extends IDatabaseAccountRequiredFields {
 | 
				
			||||||
    Dropped?: boolean;
 | 
					    Dropped?: boolean;
 | 
				
			||||||
    LatestEventMessageDate: Date;
 | 
					    LatestEventMessageDate: Date;
 | 
				
			||||||
 | 
					    LastLoginRewardDate: number;
 | 
				
			||||||
 | 
					    LoginDays: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Includes virtual ID
 | 
					// Includes virtual ID
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "DailyTributeInfo": {
 | 
					 | 
				
			||||||
    "IsMilestoneDay": false,
 | 
					 | 
				
			||||||
    "IsChooseRewardSet": true,
 | 
					 | 
				
			||||||
    "LoginDays": 1337,
 | 
					 | 
				
			||||||
    "NextMilestoneReward": "",
 | 
					 | 
				
			||||||
    "NextMilestoneDay": 50
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										187
									
								
								static/fixed_responses/loginRewards/randomRewards.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								static/fixed_responses/loginRewards/randomRewards.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,187 @@
 | 
				
			|||||||
 | 
					[
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/MiscItems/OxiumAlloy",
 | 
				
			||||||
 | 
					    "Amount": 100,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Ordis/DDayTribOrdis"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/MiscItems/Gallium",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/Research/ChemFragment",
 | 
				
			||||||
 | 
					    "Amount": 2,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/MiscItems/Morphic",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/Research/EnergyFragment",
 | 
				
			||||||
 | 
					    "Amount": 2,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/MiscItems/NeuralSensor",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/Research/BioFragment",
 | 
				
			||||||
 | 
					    "Amount": 2,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/MiscItems/Neurode",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/MiscItems/OrokinCell",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/MiscItems/Cryotic",
 | 
				
			||||||
 | 
					    "Amount": 50,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RESOURCE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Items/MiscItems/Tellurium",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Darvo/DDayTribDarvo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_CREDITS",
 | 
				
			||||||
 | 
					    "StoreItemType": "",
 | 
				
			||||||
 | 
					    "Icon": "/Lotus/Interface/Icons/StoreIcons/Currency/CreditsLarge.png",
 | 
				
			||||||
 | 
					    "Amount": 10000,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Simaris/DDayTribSmrs"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_BOOSTER",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/Types/StoreItems/Boosters/AffinityBoosterStoreItem",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 2,
 | 
				
			||||||
 | 
					    "Duration": 3,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Simaris/DDayTribSmrs"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_BOOSTER",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/Types/StoreItems/Boosters/CreditBoosterStoreItem",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 2,
 | 
				
			||||||
 | 
					    "Duration": 3,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Simaris/DDayTribSmrs"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_BOOSTER",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/Types/StoreItems/Boosters/ResourceAmountBoosterStoreItem",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 2,
 | 
				
			||||||
 | 
					    "Duration": 3,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Simaris/DDayTribSmrs"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_BOOSTER",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/Types/StoreItems/Boosters/ResourceDropChanceBoosterStoreItem",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 2,
 | 
				
			||||||
 | 
					    "Duration": 3,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Simaris/DDayTribSmrs"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_STORE_ITEM",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Upgrades/Mods/FusionBundles/RareFusionBundle",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Maroo/DDayTribMaroo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RECIPE",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/Recipes/Components/FormaBlueprint",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 0.5,
 | 
				
			||||||
 | 
					    "Rarity": "RARE",
 | 
				
			||||||
 | 
					    "probability": 0.001467351430667816,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Maroo/DDayTribMaroo"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_RANDOM_RECIPE",
 | 
				
			||||||
 | 
					    "StoreItemType": "",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 0,
 | 
				
			||||||
 | 
					    "Rarity": "COMMON",
 | 
				
			||||||
 | 
					    "probability": 0.055392516507703576,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Teshin/DDayTribTeshin"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "RewardType": "RT_STORE_ITEM",
 | 
				
			||||||
 | 
					    "StoreItemType": "/Lotus/StoreItems/Types/BoosterPacks/LoginRewardRandomProjection",
 | 
				
			||||||
 | 
					    "Amount": 1,
 | 
				
			||||||
 | 
					    "ScalingMultiplier": 1,
 | 
				
			||||||
 | 
					    "Rarity": "RARE",
 | 
				
			||||||
 | 
					    "probability": 0.001467351430667816,
 | 
				
			||||||
 | 
					    "Transmission": "/Lotus/Sounds/Dialog/DailyTribute/Ordis/DDayTribOrdis"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user