From 901263ada3a96874dc10394f8b2a735c625d80b5 Mon Sep 17 00:00:00 2001 From: Sainan Date: Sat, 8 Mar 2025 04:34:41 -0800 Subject: [PATCH] feat: transmutation (#1112) Closes #1098 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1112 Co-authored-by: Sainan Co-committed-by: Sainan --- package-lock.json | 8 +- package.json | 2 +- .../api/activateRandomModController.ts | 69 +--------- .../api/artifactTransmutationController.ts | 124 ++++++++++++++++++ .../completeRandomModChallengeController.ts | 2 +- .../api/rerollRandomModController.ts | 6 +- src/helpers/rivenFingerprintHelper.ts | 65 --------- src/helpers/rivenHelper.ts | 121 +++++++++++++++++ src/routes/api.ts | 2 + 9 files changed, 260 insertions(+), 139 deletions(-) create mode 100644 src/controllers/api/artifactTransmutationController.ts delete mode 100644 src/helpers/rivenFingerprintHelper.ts create mode 100644 src/helpers/rivenHelper.ts diff --git a/package-lock.json b/package-lock.json index 13edcb84..99bdfa62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "copyfiles": "^2.4.1", "express": "^5", "mongoose": "^8.11.0", - "warframe-public-export-plus": "^0.5.40", + "warframe-public-export-plus": "^0.5.41", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" @@ -4083,9 +4083,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.40", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.40.tgz", - "integrity": "sha512-/qr46LE/KqDdEkW4z52EG0vZP0Z8U26FscFJ2G5K5ewbQdlSVxtf5fpOnzRkAO7jWWKfgoqx7l5WUgaLSPDj0g==" + "version": "0.5.41", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.41.tgz", + "integrity": "sha512-qVOUY4UjF1cyBrBbMwD25xHSdSf9q57/CJgjHsfSE7NUu/6pBDSZzwS0iAetAukws/1V2kDvsuy8AGtOec2L1w==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index 6fcb225d..50710c25 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "copyfiles": "^2.4.1", "express": "^5", "mongoose": "^8.11.0", - "warframe-public-export-plus": "^0.5.40", + "warframe-public-export-plus": "^0.5.41", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" diff --git a/src/controllers/api/activateRandomModController.ts b/src/controllers/api/activateRandomModController.ts index bdf67212..4ac822c7 100644 --- a/src/controllers/api/activateRandomModController.ts +++ b/src/controllers/api/activateRandomModController.ts @@ -1,10 +1,9 @@ import { toOid } from "@/src/helpers/inventoryHelpers"; -import { IRivenChallenge } from "@/src/helpers/rivenFingerprintHelper"; +import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { addMods, getInventory } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; -import { getRandomElement, getRandomInt, getRandomReward } from "@/src/services/rngService"; -import { logger } from "@/src/utils/logger"; +import { getRandomElement } from "@/src/services/rngService"; import { RequestHandler } from "express"; import { ExportUpgrades } from "warframe-public-export-plus"; @@ -19,40 +18,18 @@ export const activateRandomModController: RequestHandler = async (req, res) => { } ]); const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]); - const challenge = getRandomElement(ExportUpgrades[rivenType].availableChallenges!); - const fingerprintChallenge: IRivenChallenge = { - Type: challenge.fullName, - Progress: 0, - Required: getRandomInt(challenge.countRange[0], challenge.countRange[1]) - }; - if (Math.random() < challenge.complicationChance) { - const complications: { type: string; probability: number }[] = []; - for (const complication of challenge.complications) { - complications.push({ - type: complication.fullName, - probability: complication.weight - }); - } - fingerprintChallenge.Complication = getRandomReward(complications)!.type; - logger.debug( - `riven rolled challenge ${fingerprintChallenge.Type} with complication ${fingerprintChallenge.Complication}` - ); - const complication = challenge.complications.find(x => x.fullName == fingerprintChallenge.Complication)!; - fingerprintChallenge.Required *= complication.countMultiplier; - } else { - logger.debug(`riven rolled challenge ${fingerprintChallenge.Type}`); - } + const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]); const upgradeIndex = inventory.Upgrades.push({ ItemType: rivenType, - UpgradeFingerprint: JSON.stringify({ challenge: fingerprintChallenge }) + UpgradeFingerprint: JSON.stringify(fingerprint) }) - 1; await inventory.save(); // For some reason, in this response, the UpgradeFingerprint is simply a nested object and not a string res.json({ NewMod: { - UpgradeFingerprint: { challenge: fingerprintChallenge }, - ItemType: inventory.Upgrades[upgradeIndex].ItemType, + UpgradeFingerprint: fingerprint, + ItemType: rivenType, ItemId: toOid(inventory.Upgrades[upgradeIndex]._id) } }); @@ -61,37 +38,3 @@ export const activateRandomModController: RequestHandler = async (req, res) => { interface IActiveRandomModRequest { ItemType: string; } - -const rivenRawToRealWeighted: Record = { - "/Lotus/Upgrades/Mods/Randomized/RawArchgunRandomMod": [ - "/Lotus/Upgrades/Mods/Randomized/LotusArchgunRandomModRare" - ], - "/Lotus/Upgrades/Mods/Randomized/RawMeleeRandomMod": [ - "/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare" - ], - "/Lotus/Upgrades/Mods/Randomized/RawModularMeleeRandomMod": [ - "/Lotus/Upgrades/Mods/Randomized/LotusModularMeleeRandomModRare" - ], - "/Lotus/Upgrades/Mods/Randomized/RawModularPistolRandomMod": [ - "/Lotus/Upgrades/Mods/Randomized/LotusModularPistolRandomModRare" - ], - "/Lotus/Upgrades/Mods/Randomized/RawPistolRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare"], - "/Lotus/Upgrades/Mods/Randomized/RawRifleRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare"], - "/Lotus/Upgrades/Mods/Randomized/RawShotgunRandomMod": [ - "/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare" - ], - "/Lotus/Upgrades/Mods/Randomized/RawSentinelWeaponRandomMod": [ - "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare", - "/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare" - ] -}; diff --git a/src/controllers/api/artifactTransmutationController.ts b/src/controllers/api/artifactTransmutationController.ts new file mode 100644 index 00000000..3fd52535 --- /dev/null +++ b/src/controllers/api/artifactTransmutationController.ts @@ -0,0 +1,124 @@ +import { toOid } from "@/src/helpers/inventoryHelpers"; +import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper"; +import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService"; +import { IOid } from "@/src/types/commonTypes"; +import { RequestHandler } from "express"; +import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus"; + +export const artifactTransmutationController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId); + const payload = JSON.parse(String(req.body)) as IArtifactTransmutationRequest; + + inventory.RegularCredits -= payload.Cost; + inventory.FusionPoints -= payload.FusionPointCost; + + if (payload.RivenTransmute) { + addMiscItems(inventory, [ + { + ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientSecretItem", + ItemCount: -1 + } + ]); + + payload.Consumed.forEach(upgrade => { + inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid }); + }); + + const rawRivenType = getRandomRawRivenType(); + const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]); + const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]); + + const upgradeIndex = + inventory.Upgrades.push({ + ItemType: rivenType, + UpgradeFingerprint: JSON.stringify(fingerprint) + }) - 1; + await inventory.save(); + res.json({ + NewMods: [ + { + ItemId: toOid(inventory.Upgrades[upgradeIndex]._id), + ItemType: rivenType, + UpgradeFingerprint: fingerprint + } + ] + }); + } else { + const counts: Record = { + COMMON: 0, + UNCOMMON: 0, + RARE: 0, + LEGENDARY: 0 + }; + payload.Consumed.forEach(upgrade => { + const meta = ExportUpgrades[upgrade.ItemType]; + counts[meta.rarity] += upgrade.ItemCount; + addMods(inventory, [ + { + ItemType: upgrade.ItemType, + ItemCount: upgrade.ItemCount * -1 + } + ]); + }); + + // Based on the table on https://wiki.warframe.com/w/Transmutation + const weights: Record = { + COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4, + UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10, + RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50, + LEGENDARY: 0 + }; + + const options: { uniqueName: string; rarity: TRarity }[] = []; + Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => { + if (upgrade.canBeTransmutation) { + options.push({ uniqueName, rarity: upgrade.rarity }); + } + }); + + const newModType = getRandomWeightedReward(options, weights)!.uniqueName; + addMods(inventory, [ + { + ItemType: newModType, + ItemCount: 1 + } + ]); + + await inventory.save(); + res.json({ + NewMods: [ + { + ItemType: newModType, + ItemCount: 1 + } + ] + }); + } +}; + +const getRandomRawRivenType = (): string => { + const pack = ExportBoosterPacks["/Lotus/Types/BoosterPacks/CalendarRivenPack"]; + return getRandomWeightedRewardUc(pack.components, pack.rarityWeightsPerRoll[0])!.Item; +}; + +interface IArtifactTransmutationRequest { + Upgrade: IAgnosticUpgradeClient; + LevelDiff: number; + Consumed: IAgnosticUpgradeClient[]; + Cost: number; + FusionPointCost: number; + RivenTransmute?: boolean; +} + +interface IAgnosticUpgradeClient { + ItemType: string; + ItemId: IOid; + FromSKU: boolean; + UpgradeFingerprint: string; + PendingRerollFingerprint: string; + ItemCount: number; + LastAdded: IOid; +} diff --git a/src/controllers/api/completeRandomModChallengeController.ts b/src/controllers/api/completeRandomModChallengeController.ts index ef5e7d2a..a4e3cf08 100644 --- a/src/controllers/api/completeRandomModChallengeController.ts +++ b/src/controllers/api/completeRandomModChallengeController.ts @@ -4,7 +4,7 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; -import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenFingerprintHelper"; +import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper"; import { ExportUpgrades } from "warframe-public-export-plus"; export const completeRandomModChallengeController: RequestHandler = async (req, res) => { diff --git a/src/controllers/api/rerollRandomModController.ts b/src/controllers/api/rerollRandomModController.ts index 9dc84e5f..20e7218a 100644 --- a/src/controllers/api/rerollRandomModController.ts +++ b/src/controllers/api/rerollRandomModController.ts @@ -2,11 +2,7 @@ import { RequestHandler } from "express"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { addMiscItems, getInventory } from "@/src/services/inventoryService"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; -import { - createUnveiledRivenFingerprint, - randomiseRivenStats, - RivenFingerprint -} from "@/src/helpers/rivenFingerprintHelper"; +import { createUnveiledRivenFingerprint, randomiseRivenStats, RivenFingerprint } from "@/src/helpers/rivenHelper"; import { ExportUpgrades } from "warframe-public-export-plus"; import { IOid } from "@/src/types/commonTypes"; diff --git a/src/helpers/rivenFingerprintHelper.ts b/src/helpers/rivenFingerprintHelper.ts deleted file mode 100644 index e5fa261d..00000000 --- a/src/helpers/rivenFingerprintHelper.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { IUpgrade } from "warframe-public-export-plus"; -import { getRandomElement, getRandomInt } from "../services/rngService"; - -export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint; - -export interface IVeiledRivenFingerprint { - challenge: IRivenChallenge; -} - -export interface IRivenChallenge { - Type: string; - Progress: number; - Required: number; - Complication?: string; -} - -export interface IUnveiledRivenFingerprint { - compat: string; - lim: 0; - lvl: number; - lvlReq: number; - rerolls?: number; - pol: string; - buffs: IRivenStat[]; - curses: IRivenStat[]; -} - -interface IRivenStat { - Tag: string; - Value: number; -} - -export const createUnveiledRivenFingerprint = (meta: IUpgrade): IUnveiledRivenFingerprint => { - const fingerprint: IUnveiledRivenFingerprint = { - compat: getRandomElement(meta.compatibleItems!), - lim: 0, - lvl: 0, - lvlReq: getRandomInt(8, 16), - pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"]), - buffs: [], - curses: [] - }; - randomiseRivenStats(meta, fingerprint); - return fingerprint; -}; - -export const randomiseRivenStats = (meta: IUpgrade, fingerprint: IUnveiledRivenFingerprint): void => { - fingerprint.buffs = []; - const numBuffs = 2 + Math.trunc(Math.random() * 2); // 2 or 3 - const buffEntries = meta.upgradeEntries!.filter(x => x.canBeBuff); - for (let i = 0; i != numBuffs; ++i) { - const buffIndex = Math.trunc(Math.random() * buffEntries.length); - const entry = buffEntries[buffIndex]; - fingerprint.buffs.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) }); - buffEntries.splice(buffIndex, 1); - } - - fingerprint.curses = []; - if (Math.random() < 0.5) { - const entry = getRandomElement( - meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag)) - ); - fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) }); - } -}; diff --git a/src/helpers/rivenHelper.ts b/src/helpers/rivenHelper.ts new file mode 100644 index 00000000..e3819b64 --- /dev/null +++ b/src/helpers/rivenHelper.ts @@ -0,0 +1,121 @@ +import { IUpgrade } from "warframe-public-export-plus"; +import { getRandomElement, getRandomInt, getRandomReward } from "../services/rngService"; + +export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint; + +export interface IVeiledRivenFingerprint { + challenge: IRivenChallenge; +} + +export interface IRivenChallenge { + Type: string; + Progress: number; + Required: number; + Complication?: string; +} + +export interface IUnveiledRivenFingerprint { + compat: string; + lim: 0; + lvl: number; + lvlReq: number; + rerolls?: number; + pol: string; + buffs: IRivenStat[]; + curses: IRivenStat[]; +} + +interface IRivenStat { + Tag: string; + Value: number; +} + +export const createVeiledRivenFingerprint = (meta: IUpgrade): IVeiledRivenFingerprint => { + const challenge = getRandomElement(meta.availableChallenges!); + const fingerprintChallenge: IRivenChallenge = { + Type: challenge.fullName, + Progress: 0, + Required: getRandomInt(challenge.countRange[0], challenge.countRange[1]) + }; + if (Math.random() < challenge.complicationChance) { + const complications: { type: string; probability: number }[] = []; + for (const complication of challenge.complications) { + complications.push({ + type: complication.fullName, + probability: complication.weight + }); + } + fingerprintChallenge.Complication = getRandomReward(complications)!.type; + const complication = challenge.complications.find(x => x.fullName == fingerprintChallenge.Complication)!; + fingerprintChallenge.Required *= complication.countMultiplier; + } + return { challenge: fingerprintChallenge }; +}; + +export const createUnveiledRivenFingerprint = (meta: IUpgrade): IUnveiledRivenFingerprint => { + const fingerprint: IUnveiledRivenFingerprint = { + compat: getRandomElement(meta.compatibleItems!), + lim: 0, + lvl: 0, + lvlReq: getRandomInt(8, 16), + pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"]), + buffs: [], + curses: [] + }; + randomiseRivenStats(meta, fingerprint); + return fingerprint; +}; + +export const randomiseRivenStats = (meta: IUpgrade, fingerprint: IUnveiledRivenFingerprint): void => { + fingerprint.buffs = []; + const numBuffs = 2 + Math.trunc(Math.random() * 2); // 2 or 3 + const buffEntries = meta.upgradeEntries!.filter(x => x.canBeBuff); + for (let i = 0; i != numBuffs; ++i) { + const buffIndex = Math.trunc(Math.random() * buffEntries.length); + const entry = buffEntries[buffIndex]; + fingerprint.buffs.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) }); + buffEntries.splice(buffIndex, 1); + } + + fingerprint.curses = []; + if (Math.random() < 0.5) { + const entry = getRandomElement( + meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag)) + ); + fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) }); + } +}; + +export const rivenRawToRealWeighted: Record = { + "/Lotus/Upgrades/Mods/Randomized/RawArchgunRandomMod": [ + "/Lotus/Upgrades/Mods/Randomized/LotusArchgunRandomModRare" + ], + "/Lotus/Upgrades/Mods/Randomized/RawMeleeRandomMod": [ + "/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare" + ], + "/Lotus/Upgrades/Mods/Randomized/RawModularMeleeRandomMod": [ + "/Lotus/Upgrades/Mods/Randomized/LotusModularMeleeRandomModRare" + ], + "/Lotus/Upgrades/Mods/Randomized/RawModularPistolRandomMod": [ + "/Lotus/Upgrades/Mods/Randomized/LotusModularPistolRandomModRare" + ], + "/Lotus/Upgrades/Mods/Randomized/RawPistolRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare"], + "/Lotus/Upgrades/Mods/Randomized/RawRifleRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare"], + "/Lotus/Upgrades/Mods/Randomized/RawShotgunRandomMod": [ + "/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare" + ], + "/Lotus/Upgrades/Mods/Randomized/RawSentinelWeaponRandomMod": [ + "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare", + "/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare" + ] +}; diff --git a/src/routes/api.ts b/src/routes/api.ts index bb63982e..964ebd5d 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -7,6 +7,7 @@ import { addFriendImageController } from "@/src/controllers/api/addFriendImageCo import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController"; import { archonFusionController } from "@/src/controllers/api/archonFusionController"; import { artifactsController } from "@/src/controllers/api/artifactsController"; +import { artifactTransmutationController } from "@/src/controllers/api/artifactTransmutationController"; import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootController"; import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController"; import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController"; @@ -150,6 +151,7 @@ apiRouter.post("/addFriendImage.php", addFriendImageController); apiRouter.post("/arcaneCommon.php", arcaneCommonController); apiRouter.post("/archonFusion.php", archonFusionController); apiRouter.post("/artifacts.php", artifactsController); +apiRouter.post("/artifactTransmutation.php", artifactTransmutationController); apiRouter.post("/changeDojoRoot.php", changeDojoRootController); apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);