返回提交历史
Modified
package-lock.json
+4
-4
Modified
package.json
+1
-1
Modified
src/controllers/api/activateRandomModController.ts
+6
-63
Added
src/controllers/api/artifactTransmutationController.ts
+124
-0
Modified
src/controllers/api/completeRandomModChallengeController.ts
+1
-1
Modified
src/controllers/api/rerollRandomModController.ts
+1
-5
Deleted
src/helpers/rivenFingerprintHelper.ts
+0
-65
Added
src/helpers/rivenHelper.ts
+121
-0
Modified
src/routes/api.ts
+2
-0
XFEstudio/XFESpaceNinjaServer
feat: transmutation (#1112)
Closes #1098 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1112 Co-authored-by: Sainan <sainan@calamity.inc> Co-committed-by: Sainan <sainan@calamity.inc>
901263ad
代码差异
9 个文件
+260
-139
@@ -12,7 +12,7 @@
12
12
"copyfiles": "^2.4.1",
13
13
"express": "^5",
14
14
"mongoose": "^8.11.0",
15
"warframe-public-export-plus": "^0.5.40",
15
"warframe-public-export-plus": "^0.5.41",
16
16
"warframe-riven-info": "^0.1.2",
17
17
"winston": "^3.17.0",
18
18
"winston-daily-rotate-file": "^5.0.0"
@@ -4083,9 +4083,9 @@
4083
4083
}
4084
4084
},
4085
4085
"node_modules/warframe-public-export-plus": {
4086
"version": "0.5.40",
4087
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.40.tgz",
4088
"integrity": "sha512-/qr46LE/KqDdEkW4z52EG0vZP0Z8U26FscFJ2G5K5ewbQdlSVxtf5fpOnzRkAO7jWWKfgoqx7l5WUgaLSPDj0g=="
4086
"version": "0.5.41",
4087
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.41.tgz",
4088
"integrity": "sha512-qVOUY4UjF1cyBrBbMwD25xHSdSf9q57/CJgjHsfSE7NUu/6pBDSZzwS0iAetAukws/1V2kDvsuy8AGtOec2L1w=="
4089
4089
},
4090
4090
"node_modules/warframe-riven-info": {
4091
4091
"version": "0.1.2",
@@ -17,7 +17,7 @@
17
17
"copyfiles": "^2.4.1",
18
18
"express": "^5",
19
19
"mongoose": "^8.11.0",
20
"warframe-public-export-plus": "^0.5.40",
20
"warframe-public-export-plus": "^0.5.41",
21
21
"warframe-riven-info": "^0.1.2",
22
22
"winston": "^3.17.0",
23
23
"winston-daily-rotate-file": "^5.0.0"
@@ -1,10 +1,9 @@
1
1
import { toOid } from "@/src/helpers/inventoryHelpers";
2
import { IRivenChallenge } from "@/src/helpers/rivenFingerprintHelper";
2
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
3
3
import { getJSONfromString } from "@/src/helpers/stringHelpers";
4
4
import { addMods, getInventory } from "@/src/services/inventoryService";
5
5
import { getAccountIdForRequest } from "@/src/services/loginService";
6
import { getRandomElement, getRandomInt, getRandomReward } from "@/src/services/rngService";
7
import { logger } from "@/src/utils/logger";
6
import { getRandomElement } from "@/src/services/rngService";
8
7
import { RequestHandler } from "express";
9
8
import { ExportUpgrades } from "warframe-public-export-plus";
10
9
@@ -19,40 +18,18 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
19
18
}
20
19
]);
21
20
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
22
const challenge = getRandomElement(ExportUpgrades[rivenType].availableChallenges!);
23
const fingerprintChallenge: IRivenChallenge = {
24
Type: challenge.fullName,
25
Progress: 0,
26
Required: getRandomInt(challenge.countRange[0], challenge.countRange[1])
27
};
28
if (Math.random() < challenge.complicationChance) {
29
const complications: { type: string; probability: number }[] = [];
30
for (const complication of challenge.complications) {
31
complications.push({
32
type: complication.fullName,
33
probability: complication.weight
34
});
35
}
36
fingerprintChallenge.Complication = getRandomReward(complications)!.type;
37
logger.debug(
38
`riven rolled challenge ${fingerprintChallenge.Type} with complication ${fingerprintChallenge.Complication}`
39
);
40
const complication = challenge.complications.find(x => x.fullName == fingerprintChallenge.Complication)!;
41
fingerprintChallenge.Required *= complication.countMultiplier;
42
} else {
43
logger.debug(`riven rolled challenge ${fingerprintChallenge.Type}`);
44
}
21
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
45
22
const upgradeIndex =
46
23
inventory.Upgrades.push({
47
24
ItemType: rivenType,
48
UpgradeFingerprint: JSON.stringify({ challenge: fingerprintChallenge })
25
UpgradeFingerprint: JSON.stringify(fingerprint)
49
26
}) - 1;
50
27
await inventory.save();
51
28
// For some reason, in this response, the UpgradeFingerprint is simply a nested object and not a string
52
29
res.json({
53
30
NewMod: {
54
UpgradeFingerprint: { challenge: fingerprintChallenge },
55
ItemType: inventory.Upgrades[upgradeIndex].ItemType,
31
UpgradeFingerprint: fingerprint,
32
ItemType: rivenType,
56
33
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id)
57
34
}
58
35
});
@@ -61,37 +38,3 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
61
38
interface IActiveRandomModRequest {
62
39
ItemType: string;
63
40
}
64
65
const rivenRawToRealWeighted: Record<string, string[]> = {
66
"/Lotus/Upgrades/Mods/Randomized/RawArchgunRandomMod": [
67
"/Lotus/Upgrades/Mods/Randomized/LotusArchgunRandomModRare"
68
],
69
"/Lotus/Upgrades/Mods/Randomized/RawMeleeRandomMod": [
70
"/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare"
71
],
72
"/Lotus/Upgrades/Mods/Randomized/RawModularMeleeRandomMod": [
73
"/Lotus/Upgrades/Mods/Randomized/LotusModularMeleeRandomModRare"
74
],
75
"/Lotus/Upgrades/Mods/Randomized/RawModularPistolRandomMod": [
76
"/Lotus/Upgrades/Mods/Randomized/LotusModularPistolRandomModRare"
77
],
78
"/Lotus/Upgrades/Mods/Randomized/RawPistolRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare"],
79
"/Lotus/Upgrades/Mods/Randomized/RawRifleRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare"],
80
"/Lotus/Upgrades/Mods/Randomized/RawShotgunRandomMod": [
81
"/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare"
82
],
83
"/Lotus/Upgrades/Mods/Randomized/RawSentinelWeaponRandomMod": [
84
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
85
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
86
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
87
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
88
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
89
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
90
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
91
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
92
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
93
"/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare",
94
"/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare",
95
"/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare"
96
]
97
};
@@ -0,0 +1,124 @@
1
import { toOid } from "@/src/helpers/inventoryHelpers";
2
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
3
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
4
import { getAccountIdForRequest } from "@/src/services/loginService";
5
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
6
import { IOid } from "@/src/types/commonTypes";
7
import { RequestHandler } from "express";
8
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
9
10
export const artifactTransmutationController: RequestHandler = async (req, res) => {
11
const accountId = await getAccountIdForRequest(req);
12
const inventory = await getInventory(accountId);
13
const payload = JSON.parse(String(req.body)) as IArtifactTransmutationRequest;
14
15
inventory.RegularCredits -= payload.Cost;
16
inventory.FusionPoints -= payload.FusionPointCost;
17
18
if (payload.RivenTransmute) {
19
addMiscItems(inventory, [
20
{
21
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientSecretItem",
22
ItemCount: -1
23
}
24
]);
25
26
payload.Consumed.forEach(upgrade => {
27
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
28
});
29
30
const rawRivenType = getRandomRawRivenType();
31
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]);
32
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
33
34
const upgradeIndex =
35
inventory.Upgrades.push({
36
ItemType: rivenType,
37
UpgradeFingerprint: JSON.stringify(fingerprint)
38
}) - 1;
39
await inventory.save();
40
res.json({
41
NewMods: [
42
{
43
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id),
44
ItemType: rivenType,
45
UpgradeFingerprint: fingerprint
46
}
47
]
48
});
49
} else {
50
const counts: Record<TRarity, number> = {
51
COMMON: 0,
52
UNCOMMON: 0,
53
RARE: 0,
54
LEGENDARY: 0
55
};
56
payload.Consumed.forEach(upgrade => {
57
const meta = ExportUpgrades[upgrade.ItemType];
58
counts[meta.rarity] += upgrade.ItemCount;
59
addMods(inventory, [
60
{
61
ItemType: upgrade.ItemType,
62
ItemCount: upgrade.ItemCount * -1
63
}
64
]);
65
});
66
67
// Based on the table on https://wiki.warframe.com/w/Transmutation
68
const weights: Record<TRarity, number> = {
69
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
70
UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10,
71
RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50,
72
LEGENDARY: 0
73
};
74
75
const options: { uniqueName: string; rarity: TRarity }[] = [];
76
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
77
if (upgrade.canBeTransmutation) {
78
options.push({ uniqueName, rarity: upgrade.rarity });
79
}
80
});
81
82
const newModType = getRandomWeightedReward(options, weights)!.uniqueName;
83
addMods(inventory, [
84
{
85
ItemType: newModType,
86
ItemCount: 1
87
}
88
]);
89
90
await inventory.save();
91
res.json({
92
NewMods: [
93
{
94
ItemType: newModType,
95
ItemCount: 1
96
}
97
]
98
});
99
}
100
};
101
102
const getRandomRawRivenType = (): string => {
103
const pack = ExportBoosterPacks["/Lotus/Types/BoosterPacks/CalendarRivenPack"];
104
return getRandomWeightedRewardUc(pack.components, pack.rarityWeightsPerRoll[0])!.Item;
105
};
106
107
interface IArtifactTransmutationRequest {
108
Upgrade: IAgnosticUpgradeClient;
109
LevelDiff: number;
110
Consumed: IAgnosticUpgradeClient[];
111
Cost: number;
112
FusionPointCost: number;
113
RivenTransmute?: boolean;
114
}
115
116
interface IAgnosticUpgradeClient {
117
ItemType: string;
118
ItemId: IOid;
119
FromSKU: boolean;
120
UpgradeFingerprint: string;
121
PendingRerollFingerprint: string;
122
ItemCount: number;
123
LastAdded: IOid;
124
}
@@ -4,7 +4,7 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven
4
4
import { IInventoryChanges } from "@/src/types/purchaseTypes";
5
5
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
6
6
import { getJSONfromString } from "@/src/helpers/stringHelpers";
7
import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenFingerprintHelper";
7
import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
8
8
import { ExportUpgrades } from "warframe-public-export-plus";
9
9
10
10
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
@@ -2,11 +2,7 @@ import { RequestHandler } from "express";
2
2
import { getAccountIdForRequest } from "@/src/services/loginService";
3
3
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
4
4
import { getJSONfromString } from "@/src/helpers/stringHelpers";
5
import {
6
createUnveiledRivenFingerprint,
7
randomiseRivenStats,
8
RivenFingerprint
9
} from "@/src/helpers/rivenFingerprintHelper";
5
import { createUnveiledRivenFingerprint, randomiseRivenStats, RivenFingerprint } from "@/src/helpers/rivenHelper";
10
6
import { ExportUpgrades } from "warframe-public-export-plus";
11
7
import { IOid } from "@/src/types/commonTypes";
12
8
@@ -1,65 +0,0 @@
1
import { IUpgrade } from "warframe-public-export-plus";
2
import { getRandomElement, getRandomInt } from "../services/rngService";
3
4
export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint;
5
6
export interface IVeiledRivenFingerprint {
7
challenge: IRivenChallenge;
8
}
9
10
export interface IRivenChallenge {
11
Type: string;
12
Progress: number;
13
Required: number;
14
Complication?: string;
15
}
16
17
export interface IUnveiledRivenFingerprint {
18
compat: string;
19
lim: 0;
20
lvl: number;
21
lvlReq: number;
22
rerolls?: number;
23
pol: string;
24
buffs: IRivenStat[];
25
curses: IRivenStat[];
26
}
27
28
interface IRivenStat {
29
Tag: string;
30
Value: number;
31
}
32
33
export const createUnveiledRivenFingerprint = (meta: IUpgrade): IUnveiledRivenFingerprint => {
34
const fingerprint: IUnveiledRivenFingerprint = {
35
compat: getRandomElement(meta.compatibleItems!),
36
lim: 0,
37
lvl: 0,
38
lvlReq: getRandomInt(8, 16),
39
pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"]),
40
buffs: [],
41
curses: []
42
};
43
randomiseRivenStats(meta, fingerprint);
44
return fingerprint;
45
};
46
47
export const randomiseRivenStats = (meta: IUpgrade, fingerprint: IUnveiledRivenFingerprint): void => {
48
fingerprint.buffs = [];
49
const numBuffs = 2 + Math.trunc(Math.random() * 2); // 2 or 3
50
const buffEntries = meta.upgradeEntries!.filter(x => x.canBeBuff);
51
for (let i = 0; i != numBuffs; ++i) {
52
const buffIndex = Math.trunc(Math.random() * buffEntries.length);
53
const entry = buffEntries[buffIndex];
54
fingerprint.buffs.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
55
buffEntries.splice(buffIndex, 1);
56
}
57
58
fingerprint.curses = [];
59
if (Math.random() < 0.5) {
60
const entry = getRandomElement(
61
meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag))
62
);
63
fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
64
}
65
};
@@ -0,0 +1,121 @@
1
import { IUpgrade } from "warframe-public-export-plus";
2
import { getRandomElement, getRandomInt, getRandomReward } from "../services/rngService";
3
4
export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint;
5
6
export interface IVeiledRivenFingerprint {
7
challenge: IRivenChallenge;
8
}
9
10
export interface IRivenChallenge {
11
Type: string;
12
Progress: number;
13
Required: number;
14
Complication?: string;
15
}
16
17
export interface IUnveiledRivenFingerprint {
18
compat: string;
19
lim: 0;
20
lvl: number;
21
lvlReq: number;
22
rerolls?: number;
23
pol: string;
24
buffs: IRivenStat[];
25
curses: IRivenStat[];
26
}
27
28
interface IRivenStat {
29
Tag: string;
30
Value: number;
31
}
32
33
export const createVeiledRivenFingerprint = (meta: IUpgrade): IVeiledRivenFingerprint => {
34
const challenge = getRandomElement(meta.availableChallenges!);
35
const fingerprintChallenge: IRivenChallenge = {
36
Type: challenge.fullName,
37
Progress: 0,
38
Required: getRandomInt(challenge.countRange[0], challenge.countRange[1])
39
};
40
if (Math.random() < challenge.complicationChance) {
41
const complications: { type: string; probability: number }[] = [];
42
for (const complication of challenge.complications) {
43
complications.push({
44
type: complication.fullName,
45
probability: complication.weight
46
});
47
}
48
fingerprintChallenge.Complication = getRandomReward(complications)!.type;
49
const complication = challenge.complications.find(x => x.fullName == fingerprintChallenge.Complication)!;
50
fingerprintChallenge.Required *= complication.countMultiplier;
51
}
52
return { challenge: fingerprintChallenge };
53
};
54
55
export const createUnveiledRivenFingerprint = (meta: IUpgrade): IUnveiledRivenFingerprint => {
56
const fingerprint: IUnveiledRivenFingerprint = {
57
compat: getRandomElement(meta.compatibleItems!),
58
lim: 0,
59
lvl: 0,
60
lvlReq: getRandomInt(8, 16),
61
pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"]),
62
buffs: [],
63
curses: []
64
};
65
randomiseRivenStats(meta, fingerprint);
66
return fingerprint;
67
};
68
69
export const randomiseRivenStats = (meta: IUpgrade, fingerprint: IUnveiledRivenFingerprint): void => {
70
fingerprint.buffs = [];
71
const numBuffs = 2 + Math.trunc(Math.random() * 2); // 2 or 3
72
const buffEntries = meta.upgradeEntries!.filter(x => x.canBeBuff);
73
for (let i = 0; i != numBuffs; ++i) {
74
const buffIndex = Math.trunc(Math.random() * buffEntries.length);
75
const entry = buffEntries[buffIndex];
76
fingerprint.buffs.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
77
buffEntries.splice(buffIndex, 1);
78
}
79
80
fingerprint.curses = [];
81
if (Math.random() < 0.5) {
82
const entry = getRandomElement(
83
meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag))
84
);
85
fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
86
}
87
};
88
89
export const rivenRawToRealWeighted: Record<string, string[]> = {
90
"/Lotus/Upgrades/Mods/Randomized/RawArchgunRandomMod": [
91
"/Lotus/Upgrades/Mods/Randomized/LotusArchgunRandomModRare"
92
],
93
"/Lotus/Upgrades/Mods/Randomized/RawMeleeRandomMod": [
94
"/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare"
95
],
96
"/Lotus/Upgrades/Mods/Randomized/RawModularMeleeRandomMod": [
97
"/Lotus/Upgrades/Mods/Randomized/LotusModularMeleeRandomModRare"
98
],
99
"/Lotus/Upgrades/Mods/Randomized/RawModularPistolRandomMod": [
100
"/Lotus/Upgrades/Mods/Randomized/LotusModularPistolRandomModRare"
101
],
102
"/Lotus/Upgrades/Mods/Randomized/RawPistolRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare"],
103
"/Lotus/Upgrades/Mods/Randomized/RawRifleRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare"],
104
"/Lotus/Upgrades/Mods/Randomized/RawShotgunRandomMod": [
105
"/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare"
106
],
107
"/Lotus/Upgrades/Mods/Randomized/RawSentinelWeaponRandomMod": [
108
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
109
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
110
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
111
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
112
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
113
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
114
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
115
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
116
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
117
"/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare",
118
"/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare",
119
"/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare"
120
]
121
};
@@ -7,6 +7,7 @@ import { addFriendImageController } from "@/src/controllers/api/addFriendImageCo
7
7
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
8
8
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
9
9
import { artifactsController } from "@/src/controllers/api/artifactsController";
10
import { artifactTransmutationController } from "@/src/controllers/api/artifactTransmutationController";
10
11
import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootController";
11
12
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
12
13
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
@@ -150,6 +151,7 @@ apiRouter.post("/addFriendImage.php", addFriendImageController);
150
151
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
151
152
apiRouter.post("/archonFusion.php", archonFusionController);
152
153
apiRouter.post("/artifacts.php", artifactsController);
154
apiRouter.post("/artifactTransmutation.php", artifactTransmutationController);
153
155
apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
154
156
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
155
157
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);