Fix the rewards of Second Dream #1498
							
								
								
									
										19
									
								
								.coderabbit.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.coderabbit.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
 | 
				
			||||||
 | 
					language: "en-US"
 | 
				
			||||||
 | 
					early_access: false
 | 
				
			||||||
 | 
					reviews:
 | 
				
			||||||
 | 
					    profile: "chill"
 | 
				
			||||||
 | 
					    request_changes_workflow: false
 | 
				
			||||||
 | 
					    changed_files_summary: false
 | 
				
			||||||
 | 
					    high_level_summary: false
 | 
				
			||||||
 | 
					    poem: false
 | 
				
			||||||
 | 
					    review_status: true
 | 
				
			||||||
 | 
					    commit_status: false
 | 
				
			||||||
 | 
					    collapse_walkthrough: false
 | 
				
			||||||
 | 
					    sequence_diagrams: false
 | 
				
			||||||
 | 
					    related_prs: false
 | 
				
			||||||
 | 
					    auto_review:
 | 
				
			||||||
 | 
					        enabled: true
 | 
				
			||||||
 | 
					        drafts: false
 | 
				
			||||||
 | 
					chat:
 | 
				
			||||||
 | 
					    auto_reply: true
 | 
				
			||||||
@ -15,17 +15,16 @@
 | 
				
			|||||||
        "@typescript-eslint/restrict-template-expressions": "warn",
 | 
					        "@typescript-eslint/restrict-template-expressions": "warn",
 | 
				
			||||||
        "@typescript-eslint/restrict-plus-operands": "warn",
 | 
					        "@typescript-eslint/restrict-plus-operands": "warn",
 | 
				
			||||||
        "@typescript-eslint/no-unsafe-member-access": "warn",
 | 
					        "@typescript-eslint/no-unsafe-member-access": "warn",
 | 
				
			||||||
        "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
 | 
					        "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
 | 
				
			||||||
        "@typescript-eslint/no-misused-promises": "warn",
 | 
					 | 
				
			||||||
        "@typescript-eslint/no-unsafe-argument": "error",
 | 
					        "@typescript-eslint/no-unsafe-argument": "error",
 | 
				
			||||||
        "@typescript-eslint/no-unsafe-call": "warn",
 | 
					        "@typescript-eslint/no-unsafe-call": "warn",
 | 
				
			||||||
        "@typescript-eslint/no-unsafe-assignment": "warn",
 | 
					        "@typescript-eslint/no-unsafe-assignment": "warn",
 | 
				
			||||||
        "@typescript-eslint/no-explicit-any": "warn",
 | 
					        "@typescript-eslint/no-explicit-any": "warn",
 | 
				
			||||||
        "@typescript-eslint/no-loss-of-precision": "warn",
 | 
					        "no-loss-of-precision": "warn",
 | 
				
			||||||
        "@typescript-eslint/no-unnecessary-condition": "warn",
 | 
					        "@typescript-eslint/no-unnecessary-condition": "warn",
 | 
				
			||||||
 | 
					        "@typescript-eslint/no-base-to-string": "off",
 | 
				
			||||||
        "no-case-declarations": "error",
 | 
					        "no-case-declarations": "error",
 | 
				
			||||||
        "prettier/prettier": "error",
 | 
					        "prettier/prettier": "error",
 | 
				
			||||||
        "@typescript-eslint/semi": "error",
 | 
					 | 
				
			||||||
        "no-mixed-spaces-and-tabs": "error",
 | 
					        "no-mixed-spaces-and-tabs": "error",
 | 
				
			||||||
        "require-await": "off",
 | 
					        "require-await": "off",
 | 
				
			||||||
        "@typescript-eslint/require-await": "error"
 | 
					        "@typescript-eslint/require-await": "error"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@ -17,5 +17,5 @@ jobs:
 | 
				
			|||||||
                  node-version: ${{ matrix.version }}
 | 
					                  node-version: ${{ matrix.version }}
 | 
				
			||||||
            - run: npm ci
 | 
					            - run: npm ci
 | 
				
			||||||
            - run: cp config.json.example config.json
 | 
					            - run: cp config.json.example config.json
 | 
				
			||||||
            - run: npm run build
 | 
					            - run: npm run verify
 | 
				
			||||||
            - run: npm run lint
 | 
					            - run: npm run lint
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							@ -5,6 +5,7 @@ on:
 | 
				
			|||||||
            - main
 | 
					            - main
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
    docker:
 | 
					    docker:
 | 
				
			||||||
 | 
					        if: github.repository == 'OpenWF/SpaceNinjaServer'
 | 
				
			||||||
        runs-on: ubuntu-latest
 | 
					        runs-on: ubuntu-latest
 | 
				
			||||||
        steps:
 | 
					        steps:
 | 
				
			||||||
            - name: Set up Docker buildx
 | 
					            - name: Set up Docker buildx
 | 
				
			||||||
 | 
				
			|||||||
@ -1,2 +1,4 @@
 | 
				
			|||||||
 | 
					src/routes/api.ts
 | 
				
			||||||
static/webui/libs/
 | 
					static/webui/libs/
 | 
				
			||||||
*.html
 | 
					*.html
 | 
				
			||||||
 | 
					*.md
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "recommendations": [
 | 
				
			||||||
 | 
					        "dbaeumer.vscode-eslint"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -10,8 +10,6 @@ ENV APP_SKIP_TUTORIAL=true
 | 
				
			|||||||
ENV APP_SKIP_ALL_DIALOGUE=true
 | 
					ENV APP_SKIP_ALL_DIALOGUE=true
 | 
				
			||||||
ENV APP_UNLOCK_ALL_SCANS=true
 | 
					ENV APP_UNLOCK_ALL_SCANS=true
 | 
				
			||||||
ENV APP_UNLOCK_ALL_MISSIONS=true
 | 
					ENV APP_UNLOCK_ALL_MISSIONS=true
 | 
				
			||||||
ENV APP_UNLOCK_ALL_QUESTS=true
 | 
					 | 
				
			||||||
ENV APP_COMPLETE_ALL_QUESTS=true
 | 
					 | 
				
			||||||
ENV APP_INFINITE_RESOURCES=true
 | 
					ENV APP_INFINITE_RESOURCES=true
 | 
				
			||||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
 | 
					ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
 | 
				
			||||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
 | 
					ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://discord.gg/PNNZ3asUuY)
 | 
					More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://discord.gg/PNNZ3asUuY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Project Status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This project is in active development at <https://onlyg.it/OpenWF/SpaceNinjaServer>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues?q=&type=all&state=open&labels=-4%2C-10&milestone=0&assignee=0&poster=). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## config.json
 | 
					## config.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
 | 
					- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
 | 
				
			||||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
 | 
					- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
 | 
				
			||||||
 | 
					- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@
 | 
				
			|||||||
echo Updating SpaceNinjaServer...
 | 
					echo Updating SpaceNinjaServer...
 | 
				
			||||||
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
 | 
					git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
 | 
				
			||||||
git fetch --prune
 | 
					git fetch --prune
 | 
				
			||||||
 | 
					git stash
 | 
				
			||||||
git reset --hard origin/main
 | 
					git reset --hard origin/main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if exist static\data\0\ (
 | 
					if exist static\data\0\ (
 | 
				
			||||||
@ -13,12 +14,13 @@ if exist static\data\0\ (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo Updating dependencies...
 | 
					echo Updating dependencies...
 | 
				
			||||||
call npm i
 | 
					call npm i --omit=dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
call npm run build
 | 
					call npm run build
 | 
				
			||||||
call npm run start
 | 
					if %errorlevel% == 0 (
 | 
				
			||||||
 | 
						call npm run start
 | 
				
			||||||
echo SpaceNinjaServer seems to have crashed.
 | 
						echo SpaceNinjaServer seems to have crashed.
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
:a
 | 
					:a
 | 
				
			||||||
pause > nul
 | 
					pause > nul
 | 
				
			||||||
goto a
 | 
					goto a
 | 
				
			||||||
 | 
				
			|||||||
@ -5,31 +5,45 @@
 | 
				
			|||||||
    "level": "trace"
 | 
					    "level": "trace"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "myAddress": "localhost",
 | 
					  "myAddress": "localhost",
 | 
				
			||||||
  "hubAddress": "https://localhost/api/",
 | 
					 | 
				
			||||||
  "platformCDNs": ["https://localhost/"],
 | 
					 | 
				
			||||||
  "NRS": ["localhost"],
 | 
					 | 
				
			||||||
  "httpPort": 80,
 | 
					  "httpPort": 80,
 | 
				
			||||||
  "httpsPort": 443,
 | 
					  "httpsPort": 443,
 | 
				
			||||||
 | 
					  "NRS": ["localhost"],
 | 
				
			||||||
  "administratorNames": [],
 | 
					  "administratorNames": [],
 | 
				
			||||||
  "autoCreateAccount": true,
 | 
					  "autoCreateAccount": true,
 | 
				
			||||||
  "skipTutorial": true,
 | 
					  "skipTutorial": false,
 | 
				
			||||||
  "skipAllDialogue": true,
 | 
					  "skipAllDialogue": false,
 | 
				
			||||||
  "unlockAllScans": true,
 | 
					  "unlockAllScans": false,
 | 
				
			||||||
  "unlockAllMissions": true,
 | 
					  "unlockAllMissions": false,
 | 
				
			||||||
  "infiniteCredits": true,
 | 
					  "infiniteCredits": false,
 | 
				
			||||||
  "infinitePlatinum": true,
 | 
					  "infinitePlatinum": false,
 | 
				
			||||||
  "infiniteEndo": true,
 | 
					  "infiniteEndo": false,
 | 
				
			||||||
  "infiniteRegalAya": true,
 | 
					  "infiniteRegalAya": false,
 | 
				
			||||||
  "infiniteHelminthMaterials": false,
 | 
					  "infiniteHelminthMaterials": false,
 | 
				
			||||||
  "unlockAllShipFeatures": true,
 | 
					  "unlockAllShipFeatures": false,
 | 
				
			||||||
  "unlockAllShipDecorations": true,
 | 
					  "unlockAllShipDecorations": false,
 | 
				
			||||||
  "unlockAllFlavourItems": true,
 | 
					  "unlockAllFlavourItems": false,
 | 
				
			||||||
  "unlockAllSkins": true,
 | 
					  "unlockAllSkins": false,
 | 
				
			||||||
  "unlockAllCapturaScenes": true,
 | 
					  "unlockAllCapturaScenes": false,
 | 
				
			||||||
  "universalPolarityEverywhere": true,
 | 
					  "universalPolarityEverywhere": false,
 | 
				
			||||||
  "unlockDoubleCapacityPotatoesEverywhere": true,
 | 
					  "unlockDoubleCapacityPotatoesEverywhere": false,
 | 
				
			||||||
  "unlockExilusEverywhere": true,
 | 
					  "unlockExilusEverywhere": false,
 | 
				
			||||||
  "unlockArcanesEverywhere": true,
 | 
					  "unlockArcanesEverywhere": false,
 | 
				
			||||||
  "noDailyStandingLimits": true,
 | 
					  "noDailyStandingLimits": false,
 | 
				
			||||||
  "spoofMasteryRank": -1
 | 
					  "noArgonCrystalDecay": false,
 | 
				
			||||||
 | 
					  "noMasteryRankUpCooldown": false,
 | 
				
			||||||
 | 
					  "noVendorPurchaseLimits": true,
 | 
				
			||||||
 | 
					  "instantResourceExtractorDrones": false,
 | 
				
			||||||
 | 
					  "noDojoRoomBuildStage": false,
 | 
				
			||||||
 | 
					  "fastDojoRoomDestruction": false,
 | 
				
			||||||
 | 
					  "noDojoResearchCosts": false,
 | 
				
			||||||
 | 
					  "noDojoResearchTime": false,
 | 
				
			||||||
 | 
					  "fastClanAscension": false,
 | 
				
			||||||
 | 
					  "spoofMasteryRank": -1,
 | 
				
			||||||
 | 
					  "worldState": {
 | 
				
			||||||
 | 
					    "creditBoost": false,
 | 
				
			||||||
 | 
					    "affinityBoost": false,
 | 
				
			||||||
 | 
					    "resourceBoost": false,
 | 
				
			||||||
 | 
					    "starDays": true,
 | 
				
			||||||
 | 
					    "lockTime": 0
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										962
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										962
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										33
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								package.json
									
									
									
									
									
								
							@ -6,7 +6,8 @@
 | 
				
			|||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "start": "node --import ./build/src/pathman.js build/src/index.js",
 | 
					    "start": "node --import ./build/src/pathman.js build/src/index.js",
 | 
				
			||||||
    "dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
 | 
					    "dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
 | 
				
			||||||
    "build": "tsc && copyfiles static/webui/** build",
 | 
					    "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
				
			||||||
 | 
					    "verify": "tsgo --noEmit",
 | 
				
			||||||
    "lint": "eslint --ext .ts .",
 | 
					    "lint": "eslint --ext .ts .",
 | 
				
			||||||
    "lint:fix": "eslint --fix --ext .ts .",
 | 
					    "lint:fix": "eslint --fix --ext .ts .",
 | 
				
			||||||
    "prettier": "prettier --write .",
 | 
					    "prettier": "prettier --write .",
 | 
				
			||||||
@ -14,27 +15,29 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "license": "GNU",
 | 
					  "license": "GNU",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "copyfiles": "^2.4.1",
 | 
					    "@types/express": "^5",
 | 
				
			||||||
 | 
					    "@types/morgan": "^1.9.9",
 | 
				
			||||||
 | 
					    "crc-32": "^1.2.2",
 | 
				
			||||||
    "express": "^5",
 | 
					    "express": "^5",
 | 
				
			||||||
    "mongoose": "^8.9.4",
 | 
					    "json-with-bigint": "^3.2.2",
 | 
				
			||||||
    "warframe-public-export-plus": "^0.5.36",
 | 
					    "mongoose": "^8.11.0",
 | 
				
			||||||
 | 
					    "morgan": "^1.10.0",
 | 
				
			||||||
 | 
					    "ncp": "^2.0.0",
 | 
				
			||||||
 | 
					    "typescript": ">=5.5 <5.6.0",
 | 
				
			||||||
 | 
					    "warframe-public-export-plus": "^0.5.52",
 | 
				
			||||||
    "warframe-riven-info": "^0.1.2",
 | 
					    "warframe-riven-info": "^0.1.2",
 | 
				
			||||||
    "winston": "^3.17.0",
 | 
					    "winston": "^3.17.0",
 | 
				
			||||||
    "winston-daily-rotate-file": "^5.0.0"
 | 
					    "winston-daily-rotate-file": "^5.0.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@types/express": "^5",
 | 
					    "@rxliuli/tsgo": "^2025.3.31",
 | 
				
			||||||
    "@types/morgan": "^1.9.9",
 | 
					    "@typescript-eslint/eslint-plugin": "^8.28.0",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^7.18",
 | 
					    "@typescript-eslint/parser": "^8.28.0",
 | 
				
			||||||
    "@typescript-eslint/parser": "^7.18",
 | 
					    "eslint": "^8",
 | 
				
			||||||
    "eslint": "^8.56.0",
 | 
					    "eslint-plugin-prettier": "^5.2.5",
 | 
				
			||||||
    "eslint-plugin-prettier": "^5.2.3",
 | 
					    "prettier": "^3.5.3",
 | 
				
			||||||
    "morgan": "^1.10.0",
 | 
					 | 
				
			||||||
    "prettier": "^3.4.2",
 | 
					 | 
				
			||||||
    "ts-node": "^10.9.2",
 | 
					 | 
				
			||||||
    "ts-node-dev": "^2.0.0",
 | 
					    "ts-node-dev": "^2.0.0",
 | 
				
			||||||
    "tsconfig-paths": "^4.2.0",
 | 
					    "tsconfig-paths": "^4.2.0"
 | 
				
			||||||
    "typescript": ">=4.7.4 <5.6.0"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "engines": {
 | 
					  "engines": {
 | 
				
			||||||
    "node": ">=18.15.0",
 | 
					    "node": ">=18.15.0",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
// Based on http://209.141.38.3/OpenWF/Translations/src/branch/main/update.php
 | 
					// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
 | 
				
			||||||
// Converted via ChatGPT-4o
 | 
					// Converted via ChatGPT-4o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fs = require("fs");
 | 
					const fs = require("fs");
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								src/app.ts
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/app.ts
									
									
									
									
									
								
							@ -15,6 +15,15 @@ import { webuiRouter } from "@/src/routes/webui";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const app = express();
 | 
					const app = express();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.use((req, _res, next) => {
 | 
				
			||||||
 | 
					    // 38.5.0 introduced "ezip" for encrypted body blobs.
 | 
				
			||||||
 | 
					    // The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
 | 
				
			||||||
 | 
					    if (req.headers["content-encoding"] == "ezip") {
 | 
				
			||||||
 | 
					        req.headers["content-encoding"] = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    next();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.use(bodyParser.raw());
 | 
					app.use(bodyParser.raw());
 | 
				
			||||||
app.use(express.json({ limit: "4mb" }));
 | 
					app.use(express.json({ limit: "4mb" }));
 | 
				
			||||||
app.use(bodyParser.text());
 | 
					app.use(bodyParser.text());
 | 
				
			||||||
@ -23,6 +32,7 @@ app.use(requestLogger);
 | 
				
			|||||||
app.use("/api", apiRouter);
 | 
					app.use("/api", apiRouter);
 | 
				
			||||||
app.use("/", cacheRouter);
 | 
					app.use("/", cacheRouter);
 | 
				
			||||||
app.use("/custom", customRouter);
 | 
					app.use("/custom", customRouter);
 | 
				
			||||||
 | 
					app.use("/dynamic", dynamicController);
 | 
				
			||||||
app.use("/:id/dynamic", dynamicController);
 | 
					app.use("/:id/dynamic", dynamicController);
 | 
				
			||||||
app.use("/pay", payRouter);
 | 
					app.use("/pay", payRouter);
 | 
				
			||||||
app.use("/stats", statsRouter);
 | 
					app.use("/stats", statsRouter);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/controllers/api/abandonLibraryDailyTaskController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/controllers/api/abandonLibraryDailyTaskController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const abandonLibraryDailyTaskController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					    inventory.LibraryActiveDailyTaskInfo = undefined;
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.status(200).end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/controllers/api/abortDojoComponentController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/controllers/api/abortDojoComponentController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    getDojoClient,
 | 
				
			||||||
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
 | 
					    hasAccessToDojo,
 | 
				
			||||||
 | 
					    hasGuildPermission,
 | 
				
			||||||
 | 
					    removeDojoDeco,
 | 
				
			||||||
 | 
					    removeDojoRoom
 | 
				
			||||||
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const abortDojoComponentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId LevelKeys");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        !hasAccessToDojo(inventory) ||
 | 
				
			||||||
 | 
					        !(await hasGuildPermission(
 | 
				
			||||||
 | 
					            guild,
 | 
				
			||||||
 | 
					            accountId,
 | 
				
			||||||
 | 
					            request.DecoId ? GuildPermission.Decorator : GuildPermission.Architect
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        res.json({ DojoRequestStatus: -1 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (request.DecoId) {
 | 
				
			||||||
 | 
					        removeDojoDeco(guild, request.ComponentId, request.DecoId);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        await removeDojoRoom(guild, request.ComponentId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.json(await getDojoClient(guild, 0, request.ComponentId));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IAbortDojoComponentRequest {
 | 
				
			||||||
 | 
					    DecoType?: string;
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    DecoId?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const abortDojoComponentDestructionController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId LevelKeys");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
 | 
				
			||||||
 | 
					        res.json({ DojoRequestStatus: -1 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const componentId = req.query.componentId as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guild.DojoComponents.id(componentId)!.DestructionTime = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.json(await getDojoClient(guild, 0, componentId));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,8 +1,9 @@
 | 
				
			|||||||
 | 
					import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { addMods, getInventory } from "@/src/services/inventoryService";
 | 
					import { addMods, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getRandomElement, getRandomInt, getRandomReward, IRngResult } from "@/src/services/rngService";
 | 
					import { getRandomElement } from "@/src/services/rngService";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportUpgrades } from "warframe-public-export-plus";
 | 
					import { ExportUpgrades } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -17,82 +18,23 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
    const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
 | 
					    const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
 | 
				
			||||||
    const challenge = getRandomElement(ExportUpgrades[rivenType].availableChallenges!);
 | 
					    const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
 | 
				
			||||||
    const fingerprintChallenge: IRandomModChallenge = {
 | 
					 | 
				
			||||||
        Type: challenge.fullName,
 | 
					 | 
				
			||||||
        Progress: 0,
 | 
					 | 
				
			||||||
        Required: getRandomInt(challenge.countRange[0], challenge.countRange[1])
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    if (Math.random() < challenge.complicationChance) {
 | 
					 | 
				
			||||||
        const complicationsAsRngResults: IRngResult[] = [];
 | 
					 | 
				
			||||||
        for (const complication of challenge.complications) {
 | 
					 | 
				
			||||||
            complicationsAsRngResults.push({
 | 
					 | 
				
			||||||
                type: complication.fullName,
 | 
					 | 
				
			||||||
                itemCount: 1,
 | 
					 | 
				
			||||||
                probability: complication.weight
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        fingerprintChallenge.Complication = getRandomReward(complicationsAsRngResults)!.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 upgradeIndex =
 | 
					    const upgradeIndex =
 | 
				
			||||||
        inventory.Upgrades.push({
 | 
					        inventory.Upgrades.push({
 | 
				
			||||||
            ItemType: rivenType,
 | 
					            ItemType: rivenType,
 | 
				
			||||||
            UpgradeFingerprint: JSON.stringify({ challenge: fingerprintChallenge })
 | 
					            UpgradeFingerprint: JSON.stringify(fingerprint)
 | 
				
			||||||
        }) - 1;
 | 
					        }) - 1;
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    // For some reason, in this response, the UpgradeFingerprint is simply a nested object and not a string
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        NewMod: inventory.Upgrades[upgradeIndex].toJSON()
 | 
					        NewMod: {
 | 
				
			||||||
 | 
					            UpgradeFingerprint: fingerprint,
 | 
				
			||||||
 | 
					            ItemType: rivenType,
 | 
				
			||||||
 | 
					            ItemId: toOid(inventory.Upgrades[upgradeIndex]._id)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IActiveRandomModRequest {
 | 
					interface IActiveRandomModRequest {
 | 
				
			||||||
    ItemType: string;
 | 
					    ItemType: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IRandomModChallenge {
 | 
					 | 
				
			||||||
    Type: string;
 | 
					 | 
				
			||||||
    Progress: number;
 | 
					 | 
				
			||||||
    Required: number;
 | 
					 | 
				
			||||||
    Complication?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const rivenRawToRealWeighted: Record<string, string[]> = {
 | 
					 | 
				
			||||||
    "/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"
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,25 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { IUpdateGlyphRequest } from "@/src/types/requestTypes";
 | 
					 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const addFriendImageController: RequestHandler = async (req, res) => {
 | 
					export const addFriendImageController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const json = getJSONfromString<IUpdateGlyphRequest>(String(req.body));
 | 
					    const json = getJSONfromString<IUpdateGlyphRequest>(String(req.body));
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					
 | 
				
			||||||
    inventory.ActiveAvatarImageType = json.AvatarImageType;
 | 
					    await Inventory.updateOne(
 | 
				
			||||||
    await inventory.save();
 | 
					        {
 | 
				
			||||||
 | 
					            accountOwnerId: accountId
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ActiveAvatarImageType: json.AvatarImageType
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({});
 | 
					    res.json({});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { addFriendImageController };
 | 
					interface IUpdateGlyphRequest {
 | 
				
			||||||
 | 
					    AvatarImageType: string;
 | 
				
			||||||
 | 
					    AvatarImage: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										117
									
								
								src/controllers/api/addToAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/controllers/api/addToAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { createMessage } from "@/src/services/inboxService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportFlavour } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addToAllianceController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    // Check requester is a warlord in their guild
 | 
				
			||||||
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
 | 
				
			||||||
 | 
					    if (guildMember.rank > 1) {
 | 
				
			||||||
 | 
					        res.status(400).json({ Error: 104 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check guild has invite permissions in the alliance
 | 
				
			||||||
 | 
					    const allianceMember = (await AllianceMember.findOne({
 | 
				
			||||||
 | 
					        allianceId: req.query.allianceId,
 | 
				
			||||||
 | 
					        guildId: guildMember.guildId
 | 
				
			||||||
 | 
					    }))!;
 | 
				
			||||||
 | 
					    if (!(allianceMember.Permissions & GuildPermission.Recruiter)) {
 | 
				
			||||||
 | 
					        res.status(400).json({ Error: 104 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Find clan to invite
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<IAddToAllianceRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const guilds = await Guild.find(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Name:
 | 
				
			||||||
 | 
					                payload.clanName.indexOf("#") == -1
 | 
				
			||||||
 | 
					                    ? new RegExp("^" + regexEscape(payload.clanName) + "#...$")
 | 
				
			||||||
 | 
					                    : payload.clanName
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "Name"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (guilds.length == 0) {
 | 
				
			||||||
 | 
					        res.status(400).json({ Error: 101 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (guilds.length > 1) {
 | 
				
			||||||
 | 
					        const choices: IGuildChoice[] = [];
 | 
				
			||||||
 | 
					        for (const guild of guilds) {
 | 
				
			||||||
 | 
					            choices.push({
 | 
				
			||||||
 | 
					                OriginalPlatform: 0,
 | 
				
			||||||
 | 
					                Name: guild.Name
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        res.json(choices);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Add clan as a pending alliance member
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await AllianceMember.insertOne({
 | 
				
			||||||
 | 
					            allianceId: req.query.allianceId,
 | 
				
			||||||
 | 
					            guildId: guilds[0]._id,
 | 
				
			||||||
 | 
					            Pending: true,
 | 
				
			||||||
 | 
					            Permissions: 0
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        logger.debug(`alliance invite failed due to ${String(e)}`);
 | 
				
			||||||
 | 
					        res.status(400).json({ Error: 102 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Send inbox message to founding warlord
 | 
				
			||||||
 | 
					    // TOVERIFY: Should other warlords get this as well?
 | 
				
			||||||
 | 
					    // TOVERIFY: Who/what should the sender be?
 | 
				
			||||||
 | 
					    // TOVERIFY: Should this message be highPriority?
 | 
				
			||||||
 | 
					    const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
 | 
				
			||||||
 | 
					    const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
 | 
				
			||||||
 | 
					    const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
 | 
				
			||||||
 | 
					    const alliance = (await Alliance.findById(req.query.allianceId, "Name"))!;
 | 
				
			||||||
 | 
					    await createMessage(invitedClanOwnerMember.accountId, [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            sndr: getSuffixedName(account),
 | 
				
			||||||
 | 
					            msg: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Body",
 | 
				
			||||||
 | 
					            arg: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Key: "THEIR_CLAN",
 | 
				
			||||||
 | 
					                    Tag: senderGuild.Name
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Key: "CLAN",
 | 
				
			||||||
 | 
					                    Tag: guilds[0].Name
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Key: "ALLIANCE",
 | 
				
			||||||
 | 
					                    Tag: alliance.Name
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
 | 
				
			||||||
 | 
					            icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
 | 
				
			||||||
 | 
					            contextInfo: alliance._id.toString(),
 | 
				
			||||||
 | 
					            highPriority: true,
 | 
				
			||||||
 | 
					            acceptAction: "ALLIANCE_INVITE",
 | 
				
			||||||
 | 
					            declineAction: "ALLIANCE_INVITE",
 | 
				
			||||||
 | 
					            hasAccountAction: true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IAddToAllianceRequest {
 | 
				
			||||||
 | 
					    clanName: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IGuildChoice {
 | 
				
			||||||
 | 
					    OriginalPlatform: number;
 | 
				
			||||||
 | 
					    Name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										105
									
								
								src/controllers/api/addToGuildController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/controllers/api/addToGuildController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					import { Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
 | 
					import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { createMessage } from "@/src/services/inboxService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportFlavour } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addToGuildController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ("UserName" in payload) {
 | 
				
			||||||
 | 
					        // Clan recruiter sending an invite
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const account = await Account.findOne({ DisplayName: payload.UserName });
 | 
				
			||||||
 | 
					        if (!account) {
 | 
				
			||||||
 | 
					            res.status(400).json("Username does not exist");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const inventory = await getInventory(account._id.toString(), "Settings");
 | 
				
			||||||
 | 
					        // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
 | 
				
			||||||
 | 
					        if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
 | 
				
			||||||
 | 
					            res.status(400).json("Invite restricted");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
 | 
				
			||||||
 | 
					        const senderAccount = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					        if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
 | 
				
			||||||
 | 
					            res.status(400).json("Invalid permission");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await GuildMember.insertOne({
 | 
				
			||||||
 | 
					                accountId: account._id,
 | 
				
			||||||
 | 
					                guildId: payload.GuildId.$oid,
 | 
				
			||||||
 | 
					                status: 2 // outgoing invite
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            logger.debug(`guild invite failed due to ${String(e)}`);
 | 
				
			||||||
 | 
					            res.status(400).json("User already invited to clan");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
 | 
				
			||||||
 | 
					        await createMessage(account._id, [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                sndr: getSuffixedName(senderAccount),
 | 
				
			||||||
 | 
					                msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
 | 
				
			||||||
 | 
					                arg: [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Key: "clan",
 | 
				
			||||||
 | 
					                        Tag: guild.Name
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
 | 
				
			||||||
 | 
					                icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
 | 
				
			||||||
 | 
					                contextInfo: payload.GuildId.$oid,
 | 
				
			||||||
 | 
					                highPriority: true,
 | 
				
			||||||
 | 
					                acceptAction: "GUILD_INVITE",
 | 
				
			||||||
 | 
					                declineAction: "GUILD_INVITE",
 | 
				
			||||||
 | 
					                hasAccountAction: true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const member: IGuildMemberClient = {
 | 
				
			||||||
 | 
					            _id: { $oid: account._id.toString() },
 | 
				
			||||||
 | 
					            DisplayName: account.DisplayName,
 | 
				
			||||||
 | 
					            Rank: 7,
 | 
				
			||||||
 | 
					            Status: 2
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        await fillInInventoryDataForGuildMember(member);
 | 
				
			||||||
 | 
					        res.json({ NewMember: member });
 | 
				
			||||||
 | 
					    } else if ("RequestMsg" in payload) {
 | 
				
			||||||
 | 
					        // Player applying to join a clan
 | 
				
			||||||
 | 
					        const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await GuildMember.insertOne({
 | 
				
			||||||
 | 
					                accountId,
 | 
				
			||||||
 | 
					                guildId: payload.GuildId.$oid,
 | 
				
			||||||
 | 
					                status: 1, // incoming invite
 | 
				
			||||||
 | 
					                RequestMsg: payload.RequestMsg,
 | 
				
			||||||
 | 
					                RequestExpiry: new Date(Date.now() + 14 * 86400 * 1000) // TOVERIFY: I can't find any good information about this with regards to live, but 2 weeks seem reasonable.
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            logger.debug(`guild invite failed due to ${String(e)}`);
 | 
				
			||||||
 | 
					            res.status(400).send("Already requested");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        res.end();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        logger.error(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
 | 
					        res.status(400).end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IAddToGuildRequest {
 | 
				
			||||||
 | 
					    UserName?: string;
 | 
				
			||||||
 | 
					    GuildId: IOid;
 | 
				
			||||||
 | 
					    RequestMsg?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										132
									
								
								src/controllers/api/artifactTransmutationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/controllers/api/artifactTransmutationController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					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<TRarity, number> = {
 | 
				
			||||||
 | 
					            COMMON: 0,
 | 
				
			||||||
 | 
					            UNCOMMON: 0,
 | 
				
			||||||
 | 
					            RARE: 0,
 | 
				
			||||||
 | 
					            LEGENDARY: 0
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let forcedPolarity: string | undefined;
 | 
				
			||||||
 | 
					        payload.Consumed.forEach(upgrade => {
 | 
				
			||||||
 | 
					            const meta = ExportUpgrades[upgrade.ItemType];
 | 
				
			||||||
 | 
					            counts[meta.rarity] += upgrade.ItemCount;
 | 
				
			||||||
 | 
					            addMods(inventory, [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ItemType: upgrade.ItemType,
 | 
				
			||||||
 | 
					                    ItemCount: upgrade.ItemCount * -1
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					            if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
 | 
				
			||||||
 | 
					                forcedPolarity = "AP_ATTACK";
 | 
				
			||||||
 | 
					            } else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
 | 
				
			||||||
 | 
					                forcedPolarity = "AP_DEFENSE";
 | 
				
			||||||
 | 
					            } else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/TacticTransmuteCore") {
 | 
				
			||||||
 | 
					                forcedPolarity = "AP_TACTIC";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Based on the table on https://wiki.warframe.com/w/Transmutation
 | 
				
			||||||
 | 
					        const weights: Record<TRarity, number> = {
 | 
				
			||||||
 | 
					            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 && (!forcedPolarity || upgrade.polarity == forcedPolarity)) {
 | 
				
			||||||
 | 
					                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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								src/controllers/api/cancelGuildAdvertisementController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/controllers/api/cancelGuildAdvertisementController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import { GuildAd } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getGuildForRequestEx, hasGuildPermission } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const cancelGuildAdvertisementController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Advertiser))) {
 | 
				
			||||||
 | 
					        res.status(400).end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await GuildAd.deleteOne({ GuildId: guild._id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,15 +1,22 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
					import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { IDojoComponentDatabase } from "@/src/types/guildTypes";
 | 
					import { GuildPermission, IDojoComponentDatabase } from "@/src/types/guildTypes";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const changeDojoRootController: RequestHandler = async (req, res) => {
 | 
					export const changeDojoRootController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const guild = await getGuildForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    // At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the root.
 | 
					    const inventory = await getInventory(accountId, "GuildId LevelKeys");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
 | 
				
			||||||
 | 
					        res.json({ DojoRequestStatus: -1 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const idToNode: Record<string, INode> = {};
 | 
					    const idToNode: Record<string, INode> = {};
 | 
				
			||||||
    guild.DojoComponents!.forEach(x => {
 | 
					    guild.DojoComponents.forEach(x => {
 | 
				
			||||||
        idToNode[x._id.toString()] = {
 | 
					        idToNode[x._id.toString()] = {
 | 
				
			||||||
            component: x,
 | 
					            component: x,
 | 
				
			||||||
            parent: undefined,
 | 
					            parent: undefined,
 | 
				
			||||||
@ -18,7 +25,7 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let oldRoot: INode | undefined;
 | 
					    let oldRoot: INode | undefined;
 | 
				
			||||||
    guild.DojoComponents!.forEach(x => {
 | 
					    guild.DojoComponents.forEach(x => {
 | 
				
			||||||
        const node = idToNode[x._id.toString()];
 | 
					        const node = idToNode[x._id.toString()];
 | 
				
			||||||
        if (x.pi) {
 | 
					        if (x.pi) {
 | 
				
			||||||
            idToNode[x.pi.toString()].children.push(node);
 | 
					            idToNode[x.pi.toString()].children.push(node);
 | 
				
			||||||
@ -47,7 +54,7 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
        top.children.forEach(x => stack.push(x));
 | 
					        top.children.forEach(x => stack.push(x));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    guild.DojoComponents!.forEach(x => {
 | 
					    guild.DojoComponents.forEach(x => {
 | 
				
			||||||
        x._id = idMap[x._id.toString()];
 | 
					        x._id = idMap[x._id.toString()];
 | 
				
			||||||
        if (x.pi) {
 | 
					        if (x.pi) {
 | 
				
			||||||
            x.pi = idMap[x.pi.toString()];
 | 
					            x.pi = idMap[x.pi.toString()];
 | 
				
			||||||
@ -58,7 +65,7 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json(getDojoClient(guild, 0));
 | 
					    res.json(await getDojoClient(guild, 0));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface INode {
 | 
					interface INode {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										38
									
								
								src/controllers/api/changeGuildRankController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/controllers/api/changeGuildRankController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import { GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getGuildForRequest, hasGuildPermissionEx } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const changeGuildRankController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const member = (await GuildMember.findOne({
 | 
				
			||||||
 | 
					        accountId: accountId,
 | 
				
			||||||
 | 
					        guildId: req.query.guildId as string
 | 
				
			||||||
 | 
					    }))!;
 | 
				
			||||||
 | 
					    const newRank: number = parseInt(req.query.rankChange as string);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequest(req);
 | 
				
			||||||
 | 
					    if (newRank < member.rank || !hasGuildPermissionEx(guild, member, GuildPermission.Promoter)) {
 | 
				
			||||||
 | 
					        res.status(400).json("Invalid permission");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const target = (await GuildMember.findOne({
 | 
				
			||||||
 | 
					        guildId: req.query.guildId as string,
 | 
				
			||||||
 | 
					        accountId: req.query.targetId as string
 | 
				
			||||||
 | 
					    }))!;
 | 
				
			||||||
 | 
					    target.rank = parseInt(req.query.rankChange as string);
 | 
				
			||||||
 | 
					    await target.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (newRank == 0) {
 | 
				
			||||||
 | 
					        // If we just promoted someone else to Founding Warlord, we need to demote ourselves to Warlord.
 | 
				
			||||||
 | 
					        member.rank = 1;
 | 
				
			||||||
 | 
					        await member.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        _id: req.query.targetId as string,
 | 
				
			||||||
 | 
					        Rank: newRank
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -7,9 +7,19 @@ import { getRecipe } from "@/src/services/itemDataService";
 | 
				
			|||||||
import { IOid } from "@/src/types/commonTypes";
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getInventory, updateCurrency, addItem, addMiscItems, addRecipes } from "@/src/services/inventoryService";
 | 
					import {
 | 
				
			||||||
 | 
					    getInventory,
 | 
				
			||||||
 | 
					    updateCurrency,
 | 
				
			||||||
 | 
					    addItem,
 | 
				
			||||||
 | 
					    addRecipes,
 | 
				
			||||||
 | 
					    occupySlot,
 | 
				
			||||||
 | 
					    combineInventoryChanges
 | 
				
			||||||
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
 | 
					import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IClaimCompletedRecipeRequest {
 | 
					interface IClaimCompletedRecipeRequest {
 | 
				
			||||||
    RecipeIds: IOid[];
 | 
					    RecipeIds: IOid[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -37,15 +47,36 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (req.query.cancel) {
 | 
					    if (req.query.cancel) {
 | 
				
			||||||
        const currencyChanges = updateCurrency(inventory, recipe.buildPrice * -1, false);
 | 
					        const inventoryChanges: IInventoryChanges = {
 | 
				
			||||||
        addMiscItems(inventory, recipe.ingredients);
 | 
					            ...updateCurrency(inventory, recipe.buildPrice * -1, false)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const equipmentIngredients = new Set();
 | 
				
			||||||
 | 
					        for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
 | 
				
			||||||
 | 
					            if (pendingRecipe[category]) {
 | 
				
			||||||
 | 
					                pendingRecipe[category].forEach(item => {
 | 
				
			||||||
 | 
					                    const index = inventory[category].push(item) - 1;
 | 
				
			||||||
 | 
					                    inventoryChanges[category] ??= [];
 | 
				
			||||||
 | 
					                    inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
 | 
				
			||||||
 | 
					                    equipmentIngredients.add(item.ItemType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    occupySlot(inventory, InventorySlot.WEAPONS, false);
 | 
				
			||||||
 | 
					                    inventoryChanges.WeaponBin ??= { Slots: 0 };
 | 
				
			||||||
 | 
					                    inventoryChanges.WeaponBin.Slots -= 1;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const ingredient of recipe.ingredients) {
 | 
				
			||||||
 | 
					            if (!equipmentIngredients.has(ingredient.ItemType)) {
 | 
				
			||||||
 | 
					                combineInventoryChanges(
 | 
				
			||||||
 | 
					                    inventoryChanges,
 | 
				
			||||||
 | 
					                    await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
 | 
					        res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
 | 
				
			||||||
        res.json({
 | 
					 | 
				
			||||||
            ...currencyChanges,
 | 
					 | 
				
			||||||
            MiscItems: recipe.ingredients
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
					        logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,31 +99,40 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
				
			|||||||
                inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
 | 
					                inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
 | 
				
			||||||
                inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
 | 
					                inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
 | 
				
			||||||
 | 
					            inventory.BrandedSuits!.splice(
 | 
				
			||||||
 | 
					                inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
 | 
				
			||||||
 | 
					                1
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let InventoryChanges = {};
 | 
					        let InventoryChanges = {};
 | 
				
			||||||
        if (recipe.consumeOnUse) {
 | 
					        if (recipe.consumeOnUse) {
 | 
				
			||||||
            const recipeChanges = [
 | 
					            addRecipes(inventory, [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    ItemType: pendingRecipe.ItemType,
 | 
					                    ItemType: pendingRecipe.ItemType,
 | 
				
			||||||
                    ItemCount: -1
 | 
					                    ItemCount: -1
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            ];
 | 
					            ]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
            InventoryChanges = { ...InventoryChanges, Recipes: recipeChanges };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            addRecipes(inventory, recipeChanges);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (req.query.rush) {
 | 
					        if (req.query.rush) {
 | 
				
			||||||
 | 
					            const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
 | 
				
			||||||
 | 
					            const start = end - recipe.buildTime;
 | 
				
			||||||
 | 
					            const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
 | 
				
			||||||
 | 
					            const progress = secondsElapsed / recipe.buildTime;
 | 
				
			||||||
 | 
					            logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
 | 
				
			||||||
 | 
					            const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
 | 
				
			||||||
            InventoryChanges = {
 | 
					            InventoryChanges = {
 | 
				
			||||||
                ...InventoryChanges,
 | 
					                ...InventoryChanges,
 | 
				
			||||||
                ...updateCurrency(inventory, recipe.skipBuildTimePrice, true)
 | 
					                ...updateCurrency(inventory, cost, true)
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (recipe.secretIngredientAction != "SIA_UNBRAND") {
 | 
				
			||||||
 | 
					            InventoryChanges = {
 | 
				
			||||||
 | 
					                ...InventoryChanges,
 | 
				
			||||||
 | 
					                ...(await addItem(inventory, recipe.resultType, recipe.num, false))
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        InventoryChanges = {
 | 
					 | 
				
			||||||
            ...InventoryChanges,
 | 
					 | 
				
			||||||
            ...(await addItem(inventory, recipe.resultType, recipe.num)).InventoryChanges
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        res.json({ InventoryChanges });
 | 
					        res.json({ InventoryChanges });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										31
									
								
								src/controllers/api/claimLibraryDailyTaskRewardController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/controllers/api/claimLibraryDailyTaskRewardController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const claimLibraryDailyTaskRewardController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const rewardQuantity = inventory.LibraryActiveDailyTaskInfo!.RewardQuantity;
 | 
				
			||||||
 | 
					    const rewardStanding = inventory.LibraryActiveDailyTaskInfo!.RewardStanding;
 | 
				
			||||||
 | 
					    inventory.LibraryActiveDailyTaskInfo = undefined;
 | 
				
			||||||
 | 
					    inventory.LibraryAvailableDailyTaskInfo = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let syndicate = inventory.Affiliations.find(x => x.Tag == "LibrarySyndicate");
 | 
				
			||||||
 | 
					    if (!syndicate) {
 | 
				
			||||||
 | 
					        syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: "LibrarySyndicate", Standing: 0 }) - 1];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    syndicate.Standing += rewardStanding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inventory.FusionPoints += 80 * rewardQuantity;
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        RewardItem: "/Lotus/StoreItems/Upgrades/Mods/FusionBundles/RareFusionBundle",
 | 
				
			||||||
 | 
					        RewardQuantity: rewardQuantity,
 | 
				
			||||||
 | 
					        StandingAwarded: rewardStanding,
 | 
				
			||||||
 | 
					        InventoryChanges: {
 | 
				
			||||||
 | 
					            FusionPoints: 80 * rewardQuantity
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/controllers/api/clearNewEpisodeRewardController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/controllers/api/clearNewEpisodeRewardController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// example req.body: {"NewEpisodeReward":true,"crossPlaySetting":"ENABLED"}
 | 
				
			||||||
 | 
					export const clearNewEpisodeRewardController: RequestHandler = (_req, res) => {
 | 
				
			||||||
 | 
					    res.status(200).end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -4,8 +4,7 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven
 | 
				
			|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { IUnveiledRivenFingerprint, randomiseRivenStats } from "@/src/helpers/rivenFingerprintHelper";
 | 
					import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
 | 
				
			||||||
import { getRandomElement, getRandomInt } from "@/src/services/rngService";
 | 
					 | 
				
			||||||
import { ExportUpgrades } from "warframe-public-export-plus";
 | 
					import { ExportUpgrades } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
 | 
					export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
 | 
				
			||||||
@ -31,17 +30,7 @@ export const completeRandomModChallengeController: RequestHandler = async (req,
 | 
				
			|||||||
    // Update riven fingerprint to a randomised unveiled state
 | 
					    // Update riven fingerprint to a randomised unveiled state
 | 
				
			||||||
    const upgrade = inventory.Upgrades.id(request.ItemId)!;
 | 
					    const upgrade = inventory.Upgrades.id(request.ItemId)!;
 | 
				
			||||||
    const meta = ExportUpgrades[upgrade.ItemType];
 | 
					    const meta = ExportUpgrades[upgrade.ItemType];
 | 
				
			||||||
    const fingerprint: IUnveiledRivenFingerprint = {
 | 
					    upgrade.UpgradeFingerprint = JSON.stringify(createUnveiledRivenFingerprint(meta));
 | 
				
			||||||
        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);
 | 
					 | 
				
			||||||
    upgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										37
									
								
								src/controllers/api/confirmAllianceInvitationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/controllers/api/confirmAllianceInvitationController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getAllianceClient } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const confirmAllianceInvitationController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    // Check requester is a warlord in their guild
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
 | 
				
			||||||
 | 
					    if (guildMember.rank > 1) {
 | 
				
			||||||
 | 
					        res.status(400).json({ Error: 104 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const allianceMember = await AllianceMember.findOne({
 | 
				
			||||||
 | 
					        allianceId: req.query.allianceId,
 | 
				
			||||||
 | 
					        guildId: guildMember.guildId
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (!allianceMember || !allianceMember.Pending) {
 | 
				
			||||||
 | 
					        res.status(400);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    allianceMember.Pending = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const guild = (await Guild.findById(guildMember.guildId))!;
 | 
				
			||||||
 | 
					    guild.AllianceId = allianceMember.allianceId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await Promise.all([allianceMember.save(), guild.save()]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Give client the new alliance data which uses "AllianceId" instead of "_id" in this response
 | 
				
			||||||
 | 
					    const alliance = (await Alliance.findById(allianceMember.allianceId))!;
 | 
				
			||||||
 | 
					    const { _id, ...rest } = await getAllianceClient(alliance, guild);
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        AllianceId: _id,
 | 
				
			||||||
 | 
					        ...rest
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										124
									
								
								src/controllers/api/confirmGuildInvitationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/controllers/api/confirmGuildInvitationController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,124 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
 | 
					import { deleteGuild, getGuildClient, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { addRecipes, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GET request: A player accepting an invite they got in their inbox.
 | 
				
			||||||
 | 
					export const confirmGuildInvitationGetController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const invitedGuildMember = await GuildMember.findOne({
 | 
				
			||||||
 | 
					        accountId: account._id,
 | 
				
			||||||
 | 
					        guildId: req.query.clanId as string
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (invitedGuildMember && invitedGuildMember.status == 2) {
 | 
				
			||||||
 | 
					        let inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If this account is already in a guild, we need to do cleanup first.
 | 
				
			||||||
 | 
					        const guildMember = await GuildMember.findOneAndDelete({ accountId: account._id, status: 0 });
 | 
				
			||||||
 | 
					        if (guildMember) {
 | 
				
			||||||
 | 
					            const inventory = await getInventory(account._id.toString(), "LevelKeys Recipes");
 | 
				
			||||||
 | 
					            inventoryChanges = removeDojoKeyItems(inventory);
 | 
				
			||||||
 | 
					            await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (guildMember.rank == 0) {
 | 
				
			||||||
 | 
					                await deleteGuild(guildMember.guildId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Now that we're sure this account is not in a guild right now, we can just proceed with the normal updates.
 | 
				
			||||||
 | 
					        invitedGuildMember.status = 0;
 | 
				
			||||||
 | 
					        await invitedGuildMember.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Remove pending applications for this account
 | 
				
			||||||
 | 
					        await GuildMember.deleteMany({ accountId: account._id, status: 1 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update inventory of new member
 | 
				
			||||||
 | 
					        const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
 | 
				
			||||||
 | 
					        inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
 | 
				
			||||||
 | 
					        const recipeChanges = [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
 | 
				
			||||||
 | 
					                ItemCount: 1
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					        addRecipes(inventory, recipeChanges);
 | 
				
			||||||
 | 
					        combineInventoryChanges(inventoryChanges, { Recipes: recipeChanges });
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const guild = (await Guild.findById(req.query.clanId as string))!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Add join to clan log
 | 
				
			||||||
 | 
					        guild.RosterActivity ??= [];
 | 
				
			||||||
 | 
					        guild.RosterActivity.push({
 | 
				
			||||||
 | 
					            dateTime: new Date(),
 | 
				
			||||||
 | 
					            entryType: 6,
 | 
				
			||||||
 | 
					            details: getSuffixedName(account)
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            ...(await getGuildClient(guild, account._id.toString())),
 | 
				
			||||||
 | 
					            InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        res.end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// POST request: Clan representative accepting invite(s).
 | 
				
			||||||
 | 
					export const confirmGuildInvitationPostController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const guild = (await Guild.findById(req.query.clanId as string, "Ranks RosterActivity"))!;
 | 
				
			||||||
 | 
					    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Recruiter))) {
 | 
				
			||||||
 | 
					        res.status(400).json("Invalid permission");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<{ userId: string }>(String(req.body));
 | 
				
			||||||
 | 
					    const filter: { accountId?: string; status: number } = { status: 1 };
 | 
				
			||||||
 | 
					    if (payload.userId != "all") {
 | 
				
			||||||
 | 
					        filter.accountId = payload.userId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const guildMembers = await GuildMember.find(filter);
 | 
				
			||||||
 | 
					    const newMembers: string[] = [];
 | 
				
			||||||
 | 
					    for (const guildMember of guildMembers) {
 | 
				
			||||||
 | 
					        guildMember.status = 0;
 | 
				
			||||||
 | 
					        guildMember.RequestMsg = undefined;
 | 
				
			||||||
 | 
					        guildMember.RequestExpiry = undefined;
 | 
				
			||||||
 | 
					        await guildMember.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Remove other pending applications for this account
 | 
				
			||||||
 | 
					        await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update inventory of new member
 | 
				
			||||||
 | 
					        const inventory = await getInventory(guildMember.accountId.toString(), "GuildId Recipes");
 | 
				
			||||||
 | 
					        inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
 | 
				
			||||||
 | 
					        addRecipes(inventory, [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
 | 
				
			||||||
 | 
					                ItemCount: 1
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Add join to clan log
 | 
				
			||||||
 | 
					        const account = (await Account.findOne({ _id: guildMember.accountId }))!;
 | 
				
			||||||
 | 
					        guild.RosterActivity ??= [];
 | 
				
			||||||
 | 
					        guild.RosterActivity.push({
 | 
				
			||||||
 | 
					            dateTime: new Date(),
 | 
				
			||||||
 | 
					            entryType: 6,
 | 
				
			||||||
 | 
					            details: getSuffixedName(account)
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        newMembers.push(account._id.toString());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        NewMembers: newMembers
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										104
									
								
								src/controllers/api/contributeGuildClassController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/controllers/api/contributeGuildClassController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,104 @@
 | 
				
			|||||||
 | 
					import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import { createMessage } from "@/src/services/inboxService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const contributeGuildClassController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<IContributeGuildClassRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const guild = (await Guild.findById(payload.GuildId))!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // First contributor initiates ceremony and locks the pending class.
 | 
				
			||||||
 | 
					    if (!guild.CeremonyContributors) {
 | 
				
			||||||
 | 
					        guild.CeremonyContributors = [];
 | 
				
			||||||
 | 
					        guild.CeremonyClass = guildXpToClass(guild.XP);
 | 
				
			||||||
 | 
					        guild.CeremonyEndo = 0;
 | 
				
			||||||
 | 
					        for (let i = guild.Class; i != guild.CeremonyClass; ++i) {
 | 
				
			||||||
 | 
					            guild.CeremonyEndo += (i + 1) * 1000;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guild.ClassChanges ??= [];
 | 
				
			||||||
 | 
					        guild.ClassChanges.push({
 | 
				
			||||||
 | 
					            dateTime: new Date(),
 | 
				
			||||||
 | 
					            entryType: 13,
 | 
				
			||||||
 | 
					            details: guild.CeremonyClass
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guild.CeremonyContributors.push(new Types.ObjectId(accountId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Once required contributor count is hit, the class is committed and there's 72 hours to claim endo.
 | 
				
			||||||
 | 
					    if (guild.CeremonyContributors.length == payload.RequiredContributors) {
 | 
				
			||||||
 | 
					        guild.Class = guild.CeremonyClass!;
 | 
				
			||||||
 | 
					        guild.CeremonyClass = undefined;
 | 
				
			||||||
 | 
					        guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000));
 | 
				
			||||||
 | 
					        if (!config.fastClanAscension) {
 | 
				
			||||||
 | 
					            // Send message to all active guild members
 | 
				
			||||||
 | 
					            const members = await GuildMember.find({ guildId: payload.GuildId, status: 0 }, "accountId");
 | 
				
			||||||
 | 
					            for (const member of members) {
 | 
				
			||||||
 | 
					                // somewhat unfaithful as on live the "msg" is not a loctag, but since we don't have the string, we'll let the client fill it in with "arg".
 | 
				
			||||||
 | 
					                await createMessage(member.accountId, [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        sndr: guild.Name,
 | 
				
			||||||
 | 
					                        msg: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgressDetails",
 | 
				
			||||||
 | 
					                        arg: [
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                Key: "RESETDATE",
 | 
				
			||||||
 | 
					                                Tag:
 | 
				
			||||||
 | 
					                                    guild.CeremonyResetDate.getUTCMonth() +
 | 
				
			||||||
 | 
					                                    "/" +
 | 
				
			||||||
 | 
					                                    guild.CeremonyResetDate.getUTCDate() +
 | 
				
			||||||
 | 
					                                    "/" +
 | 
				
			||||||
 | 
					                                    (guild.CeremonyResetDate.getUTCFullYear() % 100) +
 | 
				
			||||||
 | 
					                                    " " +
 | 
				
			||||||
 | 
					                                    guild.CeremonyResetDate.getUTCHours().toString().padStart(2, "0") +
 | 
				
			||||||
 | 
					                                    ":" +
 | 
				
			||||||
 | 
					                                    guild.CeremonyResetDate.getUTCMinutes().toString().padStart(2, "0")
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        sub: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgress",
 | 
				
			||||||
 | 
					                        icon: "/Lotus/Interface/Graphics/ClanTileImages/ClanEnterDojo.png",
 | 
				
			||||||
 | 
					                        highPriority: true
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Either way, endo is given to the contributor.
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "FusionPoints");
 | 
				
			||||||
 | 
					    inventory.FusionPoints += guild.CeremonyEndo!;
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        NumContributors: guild.CeremonyContributors.length,
 | 
				
			||||||
 | 
					        FusionPointReward: guild.CeremonyEndo,
 | 
				
			||||||
 | 
					        Class: guild.Class,
 | 
				
			||||||
 | 
					        CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IContributeGuildClassRequest {
 | 
				
			||||||
 | 
					    GuildId: string;
 | 
				
			||||||
 | 
					    RequiredContributors: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const guildXpToClass = (xp: number): number => {
 | 
				
			||||||
 | 
					    const cummXp = [
 | 
				
			||||||
 | 
					        0, 11000, 34000, 69000, 114000, 168000, 231000, 302000, 381000, 68000, 563000, 665000, 774000, 891000
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    let highest = 0;
 | 
				
			||||||
 | 
					    for (let i = 0; i != cummXp.length; ++i) {
 | 
				
			||||||
 | 
					        if (xp < cummXp[i]) {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        highest = i;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return highest;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										168
									
								
								src/controllers/api/contributeToDojoComponentController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								src/controllers/api/contributeToDojoComponentController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,168 @@
 | 
				
			|||||||
 | 
					import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    addGuildMemberMiscItemContribution,
 | 
				
			||||||
 | 
					    getDojoClient,
 | 
				
			||||||
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
 | 
					    hasAccessToDojo,
 | 
				
			||||||
 | 
					    processDojoBuildMaterialsGathered,
 | 
				
			||||||
 | 
					    scaleRequiredCount,
 | 
				
			||||||
 | 
					    setDojoRoomLogFunded
 | 
				
			||||||
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IDojoContributable, IGuildMemberDatabase } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IContributeToDojoComponentRequest {
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    DecoId?: string;
 | 
				
			||||||
 | 
					    DecoType?: string;
 | 
				
			||||||
 | 
					    IngredientContributions: IMiscItem[];
 | 
				
			||||||
 | 
					    RegularCredits: number;
 | 
				
			||||||
 | 
					    VaultIngredientContributions: IMiscItem[];
 | 
				
			||||||
 | 
					    VaultCredits: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const contributeToDojoComponentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					    // Any clan member should have permission to contribute although notably permission is denied if they have not crafted the dojo key and were simply invited in.
 | 
				
			||||||
 | 
					    if (!hasAccessToDojo(inventory)) {
 | 
				
			||||||
 | 
					        res.json({ DojoRequestStatus: -1 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne(
 | 
				
			||||||
 | 
					        { accountId, guildId: guild._id },
 | 
				
			||||||
 | 
					        "RegularCreditsContributed MiscItemsContributed"
 | 
				
			||||||
 | 
					    ))!;
 | 
				
			||||||
 | 
					    const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
 | 
				
			||||||
 | 
					    const component = guild.DojoComponents.id(request.ComponentId)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					    if (!component.CompletionTime) {
 | 
				
			||||||
 | 
					        // Room is in "Collecting Materials" state
 | 
				
			||||||
 | 
					        if (request.DecoId) {
 | 
				
			||||||
 | 
					            throw new Error("attempt to contribute to a deco in an unfinished room?!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
 | 
				
			||||||
 | 
					        processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, component);
 | 
				
			||||||
 | 
					        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
				
			||||||
 | 
					        if (component.CompletionTime) {
 | 
				
			||||||
 | 
					            setDojoRoomLogFunded(guild, component);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // Room is past "Collecting Materials"
 | 
				
			||||||
 | 
					        if (request.DecoId) {
 | 
				
			||||||
 | 
					            const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
 | 
				
			||||||
 | 
					            const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
 | 
				
			||||||
 | 
					            processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, deco);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        ...(await getDojoClient(guild, 0, component._id)),
 | 
				
			||||||
 | 
					        InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const processContribution = (
 | 
				
			||||||
 | 
					    guild: TGuildDatabaseDocument,
 | 
				
			||||||
 | 
					    guildMember: IGuildMemberDatabase,
 | 
				
			||||||
 | 
					    request: IContributeToDojoComponentRequest,
 | 
				
			||||||
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
 | 
					    inventoryChanges: IInventoryChanges,
 | 
				
			||||||
 | 
					    meta: IDojoBuild,
 | 
				
			||||||
 | 
					    component: IDojoContributable
 | 
				
			||||||
 | 
					): void => {
 | 
				
			||||||
 | 
					    component.RegularCredits ??= 0;
 | 
				
			||||||
 | 
					    if (request.RegularCredits) {
 | 
				
			||||||
 | 
					        component.RegularCredits += request.RegularCredits;
 | 
				
			||||||
 | 
					        inventoryChanges.RegularCredits = -request.RegularCredits;
 | 
				
			||||||
 | 
					        updateCurrency(inventory, request.RegularCredits, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guildMember.RegularCreditsContributed ??= 0;
 | 
				
			||||||
 | 
					        guildMember.RegularCreditsContributed += request.RegularCredits;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (request.VaultCredits) {
 | 
				
			||||||
 | 
					        component.RegularCredits += request.VaultCredits;
 | 
				
			||||||
 | 
					        guild.VaultRegularCredits! -= request.VaultCredits;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
 | 
				
			||||||
 | 
					        guild.VaultRegularCredits ??= 0;
 | 
				
			||||||
 | 
					        guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
 | 
				
			||||||
 | 
					        component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    component.MiscItems ??= [];
 | 
				
			||||||
 | 
					    if (request.VaultIngredientContributions.length) {
 | 
				
			||||||
 | 
					        for (const ingredientContribution of request.VaultIngredientContributions) {
 | 
				
			||||||
 | 
					            const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
 | 
				
			||||||
 | 
					            if (componentMiscItem) {
 | 
				
			||||||
 | 
					                const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
				
			||||||
 | 
					                if (
 | 
				
			||||||
 | 
					                    componentMiscItem.ItemCount + ingredientContribution.ItemCount >
 | 
				
			||||||
 | 
					                    scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    ingredientContribution.ItemCount =
 | 
				
			||||||
 | 
					                        scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                componentMiscItem.ItemCount += ingredientContribution.ItemCount;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                component.MiscItems.push(ingredientContribution);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
				
			||||||
 | 
					            vaultMiscItem.ItemCount -= ingredientContribution.ItemCount;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (request.IngredientContributions.length) {
 | 
				
			||||||
 | 
					        const miscItemChanges: IMiscItem[] = [];
 | 
				
			||||||
 | 
					        for (const ingredientContribution of request.IngredientContributions) {
 | 
				
			||||||
 | 
					            const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
 | 
				
			||||||
 | 
					            if (componentMiscItem) {
 | 
				
			||||||
 | 
					                const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
				
			||||||
 | 
					                if (
 | 
				
			||||||
 | 
					                    componentMiscItem.ItemCount + ingredientContribution.ItemCount >
 | 
				
			||||||
 | 
					                    scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    ingredientContribution.ItemCount =
 | 
				
			||||||
 | 
					                        scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                componentMiscItem.ItemCount += ingredientContribution.ItemCount;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                component.MiscItems.push(ingredientContribution);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            miscItemChanges.push({
 | 
				
			||||||
 | 
					                ItemType: ingredientContribution.ItemType,
 | 
				
			||||||
 | 
					                ItemCount: ingredientContribution.ItemCount * -1
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            addGuildMemberMiscItemContribution(guildMember, ingredientContribution);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
 | 
					        inventoryChanges.MiscItems = miscItemChanges;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
 | 
				
			||||||
 | 
					        let fullyFunded = true;
 | 
				
			||||||
 | 
					        for (const ingredient of meta.ingredients) {
 | 
				
			||||||
 | 
					            const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                !componentMiscItem ||
 | 
				
			||||||
 | 
					                componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                fullyFunded = false;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (fullyFunded) {
 | 
				
			||||||
 | 
					            component.CompletionTime = new Date(Date.now() + meta.time * 1000);
 | 
				
			||||||
 | 
					            processDojoBuildMaterialsGathered(guild, meta);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										113
									
								
								src/controllers/api/contributeToVaultController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/controllers/api/contributeToVaultController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    Alliance,
 | 
				
			||||||
 | 
					    Guild,
 | 
				
			||||||
 | 
					    GuildMember,
 | 
				
			||||||
 | 
					    TGuildDatabaseDocument,
 | 
				
			||||||
 | 
					    TGuildMemberDatabaseDocument
 | 
				
			||||||
 | 
					} from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    addGuildMemberMiscItemContribution,
 | 
				
			||||||
 | 
					    addGuildMemberShipDecoContribution,
 | 
				
			||||||
 | 
					    addVaultFusionTreasures,
 | 
				
			||||||
 | 
					    addVaultMiscItems,
 | 
				
			||||||
 | 
					    addVaultShipDecos,
 | 
				
			||||||
 | 
					    getGuildForRequestEx
 | 
				
			||||||
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    addFusionTreasures,
 | 
				
			||||||
 | 
					    addMiscItems,
 | 
				
			||||||
 | 
					    addShipDecorations,
 | 
				
			||||||
 | 
					    getInventory,
 | 
				
			||||||
 | 
					    updateCurrency
 | 
				
			||||||
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const contributeToVaultController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId RegularCredits MiscItems ShipDecorations FusionTreasures");
 | 
				
			||||||
 | 
					    const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (request.Alliance) {
 | 
				
			||||||
 | 
					        const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					        const alliance = (await Alliance.findById(guild.AllianceId!))!;
 | 
				
			||||||
 | 
					        alliance.VaultRegularCredits ??= 0;
 | 
				
			||||||
 | 
					        alliance.VaultRegularCredits += request.RegularCredits;
 | 
				
			||||||
 | 
					        if (request.FromVault) {
 | 
				
			||||||
 | 
					            guild.VaultRegularCredits! -= request.RegularCredits;
 | 
				
			||||||
 | 
					            await Promise.all([guild.save(), alliance.save()]);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            updateCurrency(inventory, request.RegularCredits, false);
 | 
				
			||||||
 | 
					            await Promise.all([inventory.save(), alliance.save()]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        res.end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let guild: TGuildDatabaseDocument;
 | 
				
			||||||
 | 
					    let guildMember: TGuildMemberDatabaseDocument | undefined;
 | 
				
			||||||
 | 
					    if (request.GuildVault) {
 | 
				
			||||||
 | 
					        guild = (await Guild.findById(request.GuildVault))!;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					        guildMember = (await GuildMember.findOne(
 | 
				
			||||||
 | 
					            { accountId, guildId: guild._id },
 | 
				
			||||||
 | 
					            "RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
 | 
				
			||||||
 | 
					        ))!;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (request.RegularCredits) {
 | 
				
			||||||
 | 
					        updateCurrency(inventory, request.RegularCredits, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guild.VaultRegularCredits ??= 0;
 | 
				
			||||||
 | 
					        guild.VaultRegularCredits += request.RegularCredits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (guildMember) {
 | 
				
			||||||
 | 
					            guildMember.RegularCreditsContributed ??= 0;
 | 
				
			||||||
 | 
					            guildMember.RegularCreditsContributed += request.RegularCredits;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (request.MiscItems.length) {
 | 
				
			||||||
 | 
					        addVaultMiscItems(guild, request.MiscItems);
 | 
				
			||||||
 | 
					        for (const item of request.MiscItems) {
 | 
				
			||||||
 | 
					            if (guildMember) {
 | 
				
			||||||
 | 
					                addGuildMemberMiscItemContribution(guildMember, item);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (request.ShipDecorations.length) {
 | 
				
			||||||
 | 
					        addVaultShipDecos(guild, request.ShipDecorations);
 | 
				
			||||||
 | 
					        for (const item of request.ShipDecorations) {
 | 
				
			||||||
 | 
					            if (guildMember) {
 | 
				
			||||||
 | 
					                addGuildMemberShipDecoContribution(guildMember, item);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (request.FusionTreasures.length) {
 | 
				
			||||||
 | 
					        addVaultFusionTreasures(guild, request.FusionTreasures);
 | 
				
			||||||
 | 
					        for (const item of request.FusionTreasures) {
 | 
				
			||||||
 | 
					            addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const promises: Promise<unknown>[] = [guild.save(), inventory.save()];
 | 
				
			||||||
 | 
					    if (guildMember) {
 | 
				
			||||||
 | 
					        promises.push(guildMember.save());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await Promise.all(promises);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IContributeToVaultRequest {
 | 
				
			||||||
 | 
					    RegularCredits: number;
 | 
				
			||||||
 | 
					    MiscItems: IMiscItem[];
 | 
				
			||||||
 | 
					    ShipDecorations: ITypeCount[];
 | 
				
			||||||
 | 
					    FusionTreasures: IFusionTreasure[];
 | 
				
			||||||
 | 
					    Alliance?: boolean;
 | 
				
			||||||
 | 
					    FromVault?: boolean;
 | 
				
			||||||
 | 
					    GuildVault?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/controllers/api/createAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/controllers/api/createAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getAllianceClient } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createAllianceController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
 | 
					    const guild = (await Guild.findById(inventory.GuildId!, "Name Tier AllianceId"))!;
 | 
				
			||||||
 | 
					    if (guild.AllianceId) {
 | 
				
			||||||
 | 
					        res.status(400).send("Guild is already in an alliance").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ guildId: guild._id, accountId }, "rank"))!;
 | 
				
			||||||
 | 
					    if (guildMember.rank > 1) {
 | 
				
			||||||
 | 
					        res.status(400).send("Invalid permission").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const data = getJSONfromString<ICreateAllianceRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const alliance = new Alliance({ Name: data.allianceName });
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await alliance.save();
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        res.status(400).send("Alliance name already in use").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    guild.AllianceId = alliance._id;
 | 
				
			||||||
 | 
					    await Promise.all([
 | 
				
			||||||
 | 
					        guild.save(),
 | 
				
			||||||
 | 
					        AllianceMember.insertOne({
 | 
				
			||||||
 | 
					            allianceId: alliance._id,
 | 
				
			||||||
 | 
					            guildId: guild._id,
 | 
				
			||||||
 | 
					            Pending: false,
 | 
				
			||||||
 | 
					            Permissions:
 | 
				
			||||||
 | 
					                GuildPermission.Ruler |
 | 
				
			||||||
 | 
					                GuildPermission.Promoter |
 | 
				
			||||||
 | 
					                GuildPermission.Recruiter |
 | 
				
			||||||
 | 
					                GuildPermission.Treasurer |
 | 
				
			||||||
 | 
					                GuildPermission.ChatModerator
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    res.json(await getAllianceClient(alliance, guild));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ICreateAllianceRequest {
 | 
				
			||||||
 | 
					    allianceName: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,35 +1,52 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
import { Guild } from "@/src/models/guildModel";
 | 
					import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { addRecipes, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createGuildController: RequestHandler = async (req, res) => {
 | 
					export const createGuildController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
 | 
					    const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove pending applications for this account
 | 
				
			||||||
 | 
					    await GuildMember.deleteMany({ accountId, status: 1 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Create guild on database
 | 
					    // Create guild on database
 | 
				
			||||||
    const guild = new Guild({
 | 
					    const guild = new Guild({
 | 
				
			||||||
        Name: payload.guildName
 | 
					        Name: await createUniqueClanName(payload.guildName)
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Update inventory
 | 
					    // Create guild member on database
 | 
				
			||||||
    const inventory = await Inventory.findOne({ accountOwnerId: accountId });
 | 
					    await GuildMember.insertOne({
 | 
				
			||||||
    if (inventory) {
 | 
					        accountId: accountId,
 | 
				
			||||||
        // Set GuildId
 | 
					        guildId: guild._id,
 | 
				
			||||||
        inventory.GuildId = guild._id;
 | 
					        status: 0,
 | 
				
			||||||
 | 
					        rank: 0
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Give clan key (TODO: This should only be a blueprint)
 | 
					    const inventory = await getInventory(accountId, "GuildId Recipes");
 | 
				
			||||||
        inventory.LevelKeys.push({
 | 
					    inventory.GuildId = guild._id;
 | 
				
			||||||
            ItemType: "/Lotus/Types/Keys/DojoKey",
 | 
					    addRecipes(inventory, [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
 | 
				
			||||||
            ItemCount: 1
 | 
					            ItemCount: 1
 | 
				
			||||||
        });
 | 
					        }
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await inventory.save();
 | 
					    res.json({
 | 
				
			||||||
    }
 | 
					        ...(await getGuildClient(guild, accountId)),
 | 
				
			||||||
 | 
					        InventoryChanges: {
 | 
				
			||||||
    res.json(guild);
 | 
					            Recipes: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
 | 
				
			||||||
 | 
					                    ItemCount: 1
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ICreateGuildRequest {
 | 
					interface ICreateGuildRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ export const creditsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        response.RegularCredits = 999999999;
 | 
					        response.RegularCredits = 999999999;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (config.infinitePlatinum) {
 | 
					    if (config.infinitePlatinum) {
 | 
				
			||||||
        response.PremiumCreditsFree = 999999999;
 | 
					        response.PremiumCreditsFree = 0;
 | 
				
			||||||
        response.PremiumCredits = 999999999;
 | 
					        response.PremiumCredits = 999999999;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Guild } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const guild = (await Guild.findById(data.g, "DojoComponents"))!;
 | 
				
			||||||
 | 
					    const component = guild.DojoComponents.id(data.c)!;
 | 
				
			||||||
 | 
					    if (req.query.act == "f") {
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            results: component.Leaderboard ?? []
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else if (req.query.act == "p") {
 | 
				
			||||||
 | 
					        const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					        component.Leaderboard ??= [];
 | 
				
			||||||
 | 
					        const entry = component.Leaderboard.find(x => x.n == account.DisplayName);
 | 
				
			||||||
 | 
					        if (entry) {
 | 
				
			||||||
 | 
					            entry.s = data.s!;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            component.Leaderboard.push({
 | 
				
			||||||
 | 
					                s: data.s!,
 | 
				
			||||||
 | 
					                n: account.DisplayName,
 | 
				
			||||||
 | 
					                r: 0
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        component.Leaderboard.sort((a, b) => a.s - b.s); // In this case, the score is the time in milliseconds, so smaller is better.
 | 
				
			||||||
 | 
					        if (component.Leaderboard.length > 10) {
 | 
				
			||||||
 | 
					            component.Leaderboard.shift();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let r = 0;
 | 
				
			||||||
 | 
					        for (const entry of component.Leaderboard) {
 | 
				
			||||||
 | 
					            entry.r = ++r;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					        res.status(200).end();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
 | 
					        throw new Error(`unknown customObstacleCourseLeaderboard act: ${String(req.query.act)}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ICustomObstacleCourseLeaderboardRequest {
 | 
				
			||||||
 | 
					    g: string;
 | 
				
			||||||
 | 
					    c: string;
 | 
				
			||||||
 | 
					    s?: number; // act=p
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/controllers/api/customizeGuildRanksController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/controllers/api/customizeGuildRanksController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission, IGuildRank } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const customizeGuildRanksController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequest(req);
 | 
				
			||||||
 | 
					    const payload = JSON.parse(String(req.body)) as ICustomizeGuildRanksRequest;
 | 
				
			||||||
 | 
					    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Ruler))) {
 | 
				
			||||||
 | 
					        res.status(400).json("Invalid permission");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    guild.Ranks = payload.GuildRanks;
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ICustomizeGuildRanksRequest {
 | 
				
			||||||
 | 
					    GuildRanks: IGuildRank[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/controllers/api/declineAllianceInviteController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/controllers/api/declineAllianceInviteController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import { AllianceMember, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const declineAllianceInviteController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    // Check requester is a warlord in their guild
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
 | 
				
			||||||
 | 
					    if (guildMember.rank > 1) {
 | 
				
			||||||
 | 
					        res.status(400).json({ Error: 104 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await AllianceMember.deleteOne({ allianceId: req.query.allianceId, guildId: guildMember.guildId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/controllers/api/declineGuildInviteController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/controllers/api/declineGuildInviteController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import { GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const declineGuildInviteController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await GuildMember.deleteOne({
 | 
				
			||||||
 | 
					        accountId: accountId,
 | 
				
			||||||
 | 
					        guildId: req.query.clanId as string
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/controllers/api/destroyDojoDecoController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/controllers/api/destroyDojoDecoController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    getDojoClient,
 | 
				
			||||||
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
 | 
					    hasAccessToDojo,
 | 
				
			||||||
 | 
					    hasGuildPermission,
 | 
				
			||||||
 | 
					    removeDojoDeco
 | 
				
			||||||
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const destroyDojoDecoController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId LevelKeys");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
 | 
				
			||||||
 | 
					        res.json({ DojoRequestStatus: -1 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    removeDojoDeco(guild, request.ComponentId, request.DecoId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.json(await getDojoClient(guild, 0, request.ComponentId));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IDestroyDojoDecoRequest {
 | 
				
			||||||
 | 
					    DecoType: string;
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    DecoId: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										67
									
								
								src/controllers/api/divvyAllianceVaultController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/controllers/api/divvyAllianceVaultController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { parallelForeach } from "@/src/utils/async-utils";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const divvyAllianceVaultController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    // Afaict, there's no way to put anything other than credits in the alliance vault (anymore?), so just no-op if this is not a request to divvy credits.
 | 
				
			||||||
 | 
					    if (req.query.credits == "1") {
 | 
				
			||||||
 | 
					        // Check requester is a warlord in their guild
 | 
				
			||||||
 | 
					        const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					        const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
 | 
				
			||||||
 | 
					        if (guildMember.rank > 1) {
 | 
				
			||||||
 | 
					            res.status(400).end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check guild has treasurer permissions in the alliance
 | 
				
			||||||
 | 
					        const allianceMember = (await AllianceMember.findOne({
 | 
				
			||||||
 | 
					            allianceId: req.query.allianceId,
 | 
				
			||||||
 | 
					            guildId: guildMember.guildId
 | 
				
			||||||
 | 
					        }))!;
 | 
				
			||||||
 | 
					        if (!(allianceMember.Permissions & GuildPermission.Treasurer)) {
 | 
				
			||||||
 | 
					            res.status(400).end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const allianceMembers = await AllianceMember.find({ allianceId: req.query.allianceId });
 | 
				
			||||||
 | 
					        const memberCounts: Record<string, number> = {};
 | 
				
			||||||
 | 
					        let totalMembers = 0;
 | 
				
			||||||
 | 
					        await parallelForeach(allianceMembers, async allianceMember => {
 | 
				
			||||||
 | 
					            const memberCount = await GuildMember.countDocuments({
 | 
				
			||||||
 | 
					                guildId: allianceMember.guildId
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            memberCounts[allianceMember.guildId.toString()] = memberCount;
 | 
				
			||||||
 | 
					            totalMembers += memberCount;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        logger.debug(`alliance has ${totalMembers} members between all its clans`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const alliance = (await Alliance.findById(allianceMember.allianceId, "VaultRegularCredits"))!;
 | 
				
			||||||
 | 
					        if (alliance.VaultRegularCredits) {
 | 
				
			||||||
 | 
					            let creditsHandedOutInTotal = 0;
 | 
				
			||||||
 | 
					            await parallelForeach(allianceMembers, async allianceMember => {
 | 
				
			||||||
 | 
					                const memberCount = memberCounts[allianceMember.guildId.toString()];
 | 
				
			||||||
 | 
					                const cutPercentage = memberCount / totalMembers;
 | 
				
			||||||
 | 
					                const creditsToHandOut = Math.trunc(alliance.VaultRegularCredits! * cutPercentage);
 | 
				
			||||||
 | 
					                logger.debug(
 | 
				
			||||||
 | 
					                    `${allianceMember.guildId.toString()} has ${memberCount} member(s) = ${Math.trunc(cutPercentage * 100)}% of alliance; giving ${creditsToHandOut} credit(s)`
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if (creditsToHandOut != 0) {
 | 
				
			||||||
 | 
					                    await Guild.updateOne(
 | 
				
			||||||
 | 
					                        { _id: allianceMember.guildId },
 | 
				
			||||||
 | 
					                        { $inc: { VaultRegularCredits: creditsToHandOut } }
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    creditsHandedOutInTotal += creditsToHandOut;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            alliance.VaultRegularCredits -= creditsHandedOutInTotal;
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                `handed out ${creditsHandedOutInTotal} credits; alliance vault now has ${alliance.VaultRegularCredits} credit(s)`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await alliance.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										76
									
								
								src/controllers/api/dojoComponentRushController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/controllers/api/dojoComponentRushController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IDojoContributable } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IDojoComponentRushRequest {
 | 
				
			||||||
 | 
					    DecoType?: string;
 | 
				
			||||||
 | 
					    DecoId?: string;
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    Amount: number;
 | 
				
			||||||
 | 
					    VaultAmount: number;
 | 
				
			||||||
 | 
					    AllianceVaultAmount: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const dojoComponentRushController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					    if (!hasAccessToDojo(inventory)) {
 | 
				
			||||||
 | 
					        res.json({ DojoRequestStatus: -1 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    const request = JSON.parse(String(req.body)) as IDojoComponentRushRequest;
 | 
				
			||||||
 | 
					    const component = guild.DojoComponents.id(request.ComponentId)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let platinumDonated = request.Amount;
 | 
				
			||||||
 | 
					    const inventoryChanges = updateCurrency(inventory, request.Amount, true);
 | 
				
			||||||
 | 
					    if (request.VaultAmount) {
 | 
				
			||||||
 | 
					        platinumDonated += request.VaultAmount;
 | 
				
			||||||
 | 
					        guild.VaultPremiumCredits! -= request.VaultAmount;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (request.DecoId) {
 | 
				
			||||||
 | 
					        const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
 | 
				
			||||||
 | 
					        const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
 | 
				
			||||||
 | 
					        processContribution(guild, deco, meta, platinumDonated);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
 | 
				
			||||||
 | 
					        processContribution(guild, component, meta, platinumDonated);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
 | 
				
			||||||
 | 
					        if (entry) {
 | 
				
			||||||
 | 
					            entry.dateTime = component.CompletionTime!;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
 | 
				
			||||||
 | 
					    guildMember.PremiumCreditsContributed ??= 0;
 | 
				
			||||||
 | 
					    guildMember.PremiumCreditsContributed += request.Amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        ...(await getDojoClient(guild, 0, component._id)),
 | 
				
			||||||
 | 
					        InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const processContribution = (
 | 
				
			||||||
 | 
					    guild: TGuildDatabaseDocument,
 | 
				
			||||||
 | 
					    component: IDojoContributable,
 | 
				
			||||||
 | 
					    meta: IDojoBuild,
 | 
				
			||||||
 | 
					    platinumDonated: number
 | 
				
			||||||
 | 
					): void => {
 | 
				
			||||||
 | 
					    const fullPlatinumCost = scaleRequiredCount(guild.Tier, meta.skipTimePrice);
 | 
				
			||||||
 | 
					    const fullDurationSeconds = meta.time;
 | 
				
			||||||
 | 
					    const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
 | 
				
			||||||
 | 
					    component.CompletionTime = new Date(
 | 
				
			||||||
 | 
					        component.CompletionTime!.getTime() - secondsPerPlatinum * platinumDonated * 1000
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    component.RushPlatinum ??= 0;
 | 
				
			||||||
 | 
					    component.RushPlatinum += platinumDonated;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,7 +1,143 @@
 | 
				
			|||||||
 | 
					import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import { addMiscItems, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { fromStoreItem } from "@/src/services/itemDataService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { getRandomInt, getRandomWeightedRewardUc } from "@/src/services/rngService";
 | 
				
			||||||
 | 
					import { IMongoDate, IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					import { IDroneClient } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportDrones, ExportResources, ExportSystems } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dronesController: RequestHandler = (_req, res) => {
 | 
					export const dronesController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    res.json({});
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    if ("GetActive" in req.query) {
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId, "Drones");
 | 
				
			||||||
 | 
					        const activeDrones: IActiveDrone[] = [];
 | 
				
			||||||
 | 
					        for (const drone of inventory.Drones) {
 | 
				
			||||||
 | 
					            if (drone.DeployTime) {
 | 
				
			||||||
 | 
					                activeDrones.push({
 | 
				
			||||||
 | 
					                    DeployTime: toMongoDate(drone.DeployTime),
 | 
				
			||||||
 | 
					                    System: drone.System!,
 | 
				
			||||||
 | 
					                    ItemId: toOid(drone._id),
 | 
				
			||||||
 | 
					                    ItemType: drone.ItemType,
 | 
				
			||||||
 | 
					                    CurrentHP: drone.CurrentHP,
 | 
				
			||||||
 | 
					                    DamageTime: toMongoDate(drone.DamageTime!),
 | 
				
			||||||
 | 
					                    PendingDamage: drone.PendingDamage!,
 | 
				
			||||||
 | 
					                    Resources: [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            ItemType: drone.ResourceType!,
 | 
				
			||||||
 | 
					                            BinTotal: drone.ResourceCount!,
 | 
				
			||||||
 | 
					                            StartTime: toMongoDate(drone.DeployTime)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            ActiveDrones: activeDrones
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else if ("droneId" in req.query && "systemIndex" in req.query) {
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId, "Drones");
 | 
				
			||||||
 | 
					        const drone = inventory.Drones.id(req.query.droneId as string)!;
 | 
				
			||||||
 | 
					        const droneMeta = ExportDrones[drone.ItemType];
 | 
				
			||||||
 | 
					        drone.DeployTime = config.instantResourceExtractorDrones ? new Date(0) : new Date();
 | 
				
			||||||
 | 
					        if (drone.RepairStart) {
 | 
				
			||||||
 | 
					            const repairMinutes = (Date.now() - drone.RepairStart.getTime()) / 60_000;
 | 
				
			||||||
 | 
					            const hpPerMinute = droneMeta.repairRate / 60;
 | 
				
			||||||
 | 
					            drone.CurrentHP = Math.min(drone.CurrentHP + Math.round(repairMinutes * hpPerMinute), droneMeta.durability);
 | 
				
			||||||
 | 
					            drone.RepairStart = undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        drone.System = parseInt(req.query.systemIndex as string);
 | 
				
			||||||
 | 
					        const system = ExportSystems[drone.System - 1];
 | 
				
			||||||
 | 
					        drone.DamageTime = config.instantResourceExtractorDrones
 | 
				
			||||||
 | 
					            ? new Date()
 | 
				
			||||||
 | 
					            : new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
 | 
				
			||||||
 | 
					        drone.PendingDamage =
 | 
				
			||||||
 | 
					            Math.random() < system.damageChance
 | 
				
			||||||
 | 
					                ? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
 | 
				
			||||||
 | 
					                : 0;
 | 
				
			||||||
 | 
					        const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
 | 
				
			||||||
 | 
					        //logger.debug(`drone rolled`, resource);
 | 
				
			||||||
 | 
					        drone.ResourceType = fromStoreItem(resource.StoreItem);
 | 
				
			||||||
 | 
					        const resourceMeta = ExportResources[drone.ResourceType];
 | 
				
			||||||
 | 
					        if (resourceMeta.pickupQuantity) {
 | 
				
			||||||
 | 
					            const pickupsToCollect = droneMeta.binCapacity * droneMeta.capacityMultipliers[resource.Rarity];
 | 
				
			||||||
 | 
					            drone.ResourceCount = 0;
 | 
				
			||||||
 | 
					            for (let i = 0; i != pickupsToCollect; ++i) {
 | 
				
			||||||
 | 
					                drone.ResourceCount += getRandomInt(
 | 
				
			||||||
 | 
					                    resourceMeta.pickupQuantity.minValue,
 | 
				
			||||||
 | 
					                    resourceMeta.pickupQuantity.maxValue
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            drone.ResourceCount = 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        res.json({});
 | 
				
			||||||
 | 
					    } else if ("collectDroneId" in req.query) {
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					        const drone = inventory.Drones.id(req.query.collectDroneId as string)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (new Date() >= drone.DamageTime!) {
 | 
				
			||||||
 | 
					            drone.CurrentHP -= drone.PendingDamage!;
 | 
				
			||||||
 | 
					            drone.RepairStart = new Date();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					        if (drone.CurrentHP <= 0) {
 | 
				
			||||||
 | 
					            inventory.RegularCredits += 100;
 | 
				
			||||||
 | 
					            inventoryChanges.RegularCredits = 100;
 | 
				
			||||||
 | 
					            inventory.Drones.pull({ _id: req.query.collectDroneId as string });
 | 
				
			||||||
 | 
					            inventoryChanges.RemovedIdItems = [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ItemId: { $oid: req.query.collectDroneId }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const completionTime = drone.DeployTime!.getTime() + ExportDrones[drone.ItemType].fillRate * 3600_000;
 | 
				
			||||||
 | 
					            if (Date.now() >= completionTime) {
 | 
				
			||||||
 | 
					                const miscItemChanges = [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        ItemType: drone.ResourceType!,
 | 
				
			||||||
 | 
					                        ItemCount: drone.ResourceCount!
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					                addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
 | 
					                inventoryChanges.MiscItems = miscItemChanges;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            drone.DeployTime = undefined;
 | 
				
			||||||
 | 
					            drone.System = undefined;
 | 
				
			||||||
 | 
					            drone.DamageTime = undefined;
 | 
				
			||||||
 | 
					            drone.PendingDamage = undefined;
 | 
				
			||||||
 | 
					            drone.ResourceType = undefined;
 | 
				
			||||||
 | 
					            drone.ResourceCount = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            inventoryChanges.Drones = [drone.toJSON<IDroneClient>()];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        throw new Error(`drones.php query not handled`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { dronesController };
 | 
					interface IActiveDrone {
 | 
				
			||||||
 | 
					    DeployTime: IMongoDate;
 | 
				
			||||||
 | 
					    System: number;
 | 
				
			||||||
 | 
					    ItemId: IOid;
 | 
				
			||||||
 | 
					    ItemType: string;
 | 
				
			||||||
 | 
					    CurrentHP: number;
 | 
				
			||||||
 | 
					    DamageTime: IMongoDate;
 | 
				
			||||||
 | 
					    PendingDamage: number;
 | 
				
			||||||
 | 
					    Resources: {
 | 
				
			||||||
 | 
					        ItemType: string;
 | 
				
			||||||
 | 
					        BinTotal: number;
 | 
				
			||||||
 | 
					        StartTime: IMongoDate;
 | 
				
			||||||
 | 
					    }[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										69
									
								
								src/controllers/api/entratiLabConquestModeController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/controllers/api/entratiLabConquestModeController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const entratiLabConquestModeController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(
 | 
				
			||||||
 | 
					        accountId,
 | 
				
			||||||
 | 
					        "EntratiVaultCountResetDate EntratiVaultCountLastPeriod EntratiLabConquestUnlocked EchoesHexConquestUnlocked EchoesHexConquestActiveFrameVariants EchoesHexConquestActiveStickers EntratiLabConquestActiveFrameVariants EntratiLabConquestCacheScoreMission EchoesHexConquestCacheScoreMission"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const body = getJSONfromString<IEntratiLabConquestModeRequest>(String(req.body));
 | 
				
			||||||
 | 
					    if (!inventory.EntratiVaultCountResetDate || Date.now() >= inventory.EntratiVaultCountResetDate.getTime()) {
 | 
				
			||||||
 | 
					        const EPOCH = 1734307200 * 1000; // Mondays, amirite?
 | 
				
			||||||
 | 
					        const day = Math.trunc((Date.now() - EPOCH) / 86400000);
 | 
				
			||||||
 | 
					        const week = Math.trunc(day / 7);
 | 
				
			||||||
 | 
					        const weekStart = EPOCH + week * 604800000;
 | 
				
			||||||
 | 
					        const weekEnd = weekStart + 604800000;
 | 
				
			||||||
 | 
					        inventory.EntratiVaultCountLastPeriod = 0;
 | 
				
			||||||
 | 
					        inventory.EntratiVaultCountResetDate = new Date(weekEnd);
 | 
				
			||||||
 | 
					        if (inventory.EntratiLabConquestUnlocked) {
 | 
				
			||||||
 | 
					            inventory.EntratiLabConquestUnlocked = 0;
 | 
				
			||||||
 | 
					            inventory.EntratiLabConquestActiveFrameVariants = [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (inventory.EchoesHexConquestUnlocked) {
 | 
				
			||||||
 | 
					            inventory.EchoesHexConquestUnlocked = 0;
 | 
				
			||||||
 | 
					            inventory.EchoesHexConquestActiveFrameVariants = [];
 | 
				
			||||||
 | 
					            inventory.EchoesHexConquestActiveStickers = [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (body.BuyMode) {
 | 
				
			||||||
 | 
					        inventory.EntratiVaultCountLastPeriod! += 2;
 | 
				
			||||||
 | 
					        if (body.IsEchoesDeepArchemedea) {
 | 
				
			||||||
 | 
					            inventory.EchoesHexConquestUnlocked = 1;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            inventory.EntratiLabConquestUnlocked = 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (body.IsEchoesDeepArchemedea) {
 | 
				
			||||||
 | 
					        if (inventory.EchoesHexConquestUnlocked) {
 | 
				
			||||||
 | 
					            inventory.EchoesHexConquestActiveFrameVariants = body.EchoesHexConquestActiveFrameVariants!;
 | 
				
			||||||
 | 
					            inventory.EchoesHexConquestActiveStickers = body.EchoesHexConquestActiveStickers!;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        if (inventory.EntratiLabConquestUnlocked) {
 | 
				
			||||||
 | 
					            inventory.EntratiLabConquestActiveFrameVariants = body.EntratiLabConquestActiveFrameVariants!;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        EntratiVaultCountResetDate: toMongoDate(inventory.EntratiVaultCountResetDate),
 | 
				
			||||||
 | 
					        EntratiVaultCountLastPeriod: inventory.EntratiVaultCountLastPeriod,
 | 
				
			||||||
 | 
					        EntratiLabConquestUnlocked: inventory.EntratiLabConquestUnlocked,
 | 
				
			||||||
 | 
					        EntratiLabConquestCacheScoreMission: inventory.EntratiLabConquestCacheScoreMission,
 | 
				
			||||||
 | 
					        EchoesHexConquestUnlocked: inventory.EchoesHexConquestUnlocked,
 | 
				
			||||||
 | 
					        EchoesHexConquestCacheScoreMission: inventory.EchoesHexConquestCacheScoreMission
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IEntratiLabConquestModeRequest {
 | 
				
			||||||
 | 
					    BuyMode?: number;
 | 
				
			||||||
 | 
					    IsEchoesDeepArchemedea?: number;
 | 
				
			||||||
 | 
					    EntratiLabConquestUnlocked?: number;
 | 
				
			||||||
 | 
					    EntratiLabConquestActiveFrameVariants?: string[];
 | 
				
			||||||
 | 
					    EchoesHexConquestUnlocked?: number;
 | 
				
			||||||
 | 
					    EchoesHexConquestActiveFrameVariants?: string[];
 | 
				
			||||||
 | 
					    EchoesHexConquestActiveStickers?: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -17,7 +17,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
 | 
					            recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
 | 
					        const item = inventory[payload.Category].id(req.query.ItemId as string)!;
 | 
				
			||||||
        item.Features ??= 0;
 | 
					        item.Features ??= 0;
 | 
				
			||||||
        item.Features |= EquipmentFeatures.INCARNON_GENESIS;
 | 
					        item.Features |= EquipmentFeatures.INCARNON_GENESIS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -39,7 +39,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
 | 
					        const item = inventory[payload.Category].id(req.query.ItemId as string)!;
 | 
				
			||||||
        item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
 | 
					        item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
 | 
					        throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,11 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getInventory, addMiscItems, addEquipment } from "@/src/services/inventoryService";
 | 
					import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/services/inventoryService";
 | 
				
			||||||
import { IMiscItem, TFocusPolarity, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { ExportFocusUpgrades } from "warframe-public-export-plus";
 | 
					import { ExportFocusUpgrades } from "warframe-public-export-plus";
 | 
				
			||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const focusController: RequestHandler = async (req, res) => {
 | 
					export const focusController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -17,17 +18,15 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        case FocusOperation.InstallLens: {
 | 
					        case FocusOperation.InstallLens: {
 | 
				
			||||||
            const request = JSON.parse(String(req.body)) as ILensInstallRequest;
 | 
					            const request = JSON.parse(String(req.body)) as ILensInstallRequest;
 | 
				
			||||||
            const inventory = await getInventory(accountId);
 | 
					            const inventory = await getInventory(accountId);
 | 
				
			||||||
            for (const item of inventory[request.Category]) {
 | 
					            const item = inventory[request.Category].id(request.WeaponId);
 | 
				
			||||||
                if (item._id.toString() == request.WeaponId) {
 | 
					            if (item) {
 | 
				
			||||||
                    item.FocusLens = request.LensType;
 | 
					                item.FocusLens = request.LensType;
 | 
				
			||||||
                    addMiscItems(inventory, [
 | 
					                addMiscItems(inventory, [
 | 
				
			||||||
                        {
 | 
					                    {
 | 
				
			||||||
                            ItemType: request.LensType,
 | 
					                        ItemType: request.LensType,
 | 
				
			||||||
                            ItemCount: -1
 | 
					                        ItemCount: -1
 | 
				
			||||||
                        } satisfies IMiscItem
 | 
					                    } satisfies IMiscItem
 | 
				
			||||||
                    ]);
 | 
					                ]);
 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json({
 | 
					            res.json({
 | 
				
			||||||
@ -55,9 +54,16 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        case FocusOperation.ActivateWay: {
 | 
					        case FocusOperation.ActivateWay: {
 | 
				
			||||||
            const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
 | 
					            const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
 | 
				
			||||||
            const inventory = await getInventory(accountId);
 | 
					
 | 
				
			||||||
            inventory.FocusAbility = focusType;
 | 
					            await Inventory.updateOne(
 | 
				
			||||||
            await inventory.save();
 | 
					                {
 | 
				
			||||||
 | 
					                    accountOwnerId: accountId
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    FocusAbility: focusType
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            res.end();
 | 
					            res.end();
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -105,6 +111,7 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            ];
 | 
					            ];
 | 
				
			||||||
            const inventory = await getInventory(accountId);
 | 
					            const inventory = await getInventory(accountId);
 | 
				
			||||||
            const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
 | 
					            const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
 | 
				
			||||||
 | 
					            occupySlot(inventory, InventorySlot.AMPS, false);
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
 | 
					            res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
				
			|||||||
@ -23,12 +23,11 @@ export const fusionTreasuresController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const request = JSON.parse(String(req.body)) as IFusionTreasureRequest;
 | 
					    const request = JSON.parse(String(req.body)) as IFusionTreasureRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Swap treasures
 | 
				
			||||||
    const oldTreasure = parseFusionTreasure(request.oldTreasureName, -1);
 | 
					    const oldTreasure = parseFusionTreasure(request.oldTreasureName, -1);
 | 
				
			||||||
    const newTreasure = parseFusionTreasure(request.newTreasureName, 1);
 | 
					    const newTreasure = parseFusionTreasure(request.newTreasureName, 1);
 | 
				
			||||||
 | 
					    const fusionTreasureChanges = [oldTreasure, newTreasure];
 | 
				
			||||||
    // Swap treasures
 | 
					    addFusionTreasures(inventory, fusionTreasureChanges);
 | 
				
			||||||
    addFusionTreasures(inventory, [oldTreasure]);
 | 
					 | 
				
			||||||
    addFusionTreasures(inventory, [newTreasure]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Remove consumed stars
 | 
					    // Remove consumed stars
 | 
				
			||||||
    const miscItemChanges: IMiscItem[] = [];
 | 
					    const miscItemChanges: IMiscItem[] = [];
 | 
				
			||||||
@ -45,5 +44,9 @@ export const fusionTreasuresController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    addMiscItems(inventory, miscItemChanges);
 | 
					    addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    // The response itself is the inventory changes for this endpoint.
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        MiscItems: miscItemChanges,
 | 
				
			||||||
 | 
					        FusionTreasures: fusionTreasureChanges
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -10,8 +10,7 @@ import { IGenericUpdate } from "@/src/types/genericUpdate";
 | 
				
			|||||||
const genericUpdateController: RequestHandler = async (request, response) => {
 | 
					const genericUpdateController: RequestHandler = async (request, response) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(request);
 | 
					    const accountId = await getAccountIdForRequest(request);
 | 
				
			||||||
    const update = getJSONfromString<IGenericUpdate>(String(request.body));
 | 
					    const update = getJSONfromString<IGenericUpdate>(String(request.body));
 | 
				
			||||||
    await updateGeneric(update, accountId);
 | 
					    response.json(await updateGeneric(update, accountId));
 | 
				
			||||||
    response.json(update);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { genericUpdateController };
 | 
					export { genericUpdateController };
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,25 @@
 | 
				
			|||||||
 | 
					import { Alliance, Guild } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getAllianceClient } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getAllianceController: RequestHandler = (_req, res) => {
 | 
					export const getAllianceController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    res.sendStatus(200);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
 | 
					    if (inventory.GuildId) {
 | 
				
			||||||
 | 
					        const guild = (await Guild.findById(inventory.GuildId, "Name Tier AllianceId"))!;
 | 
				
			||||||
 | 
					        if (guild.AllianceId) {
 | 
				
			||||||
 | 
					            const alliance = (await Alliance.findById(guild.AllianceId))!;
 | 
				
			||||||
 | 
					            res.json(await getAllianceClient(alliance, guild));
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { getAllianceController };
 | 
					/*interface IGetAllianceRequest {
 | 
				
			||||||
 | 
					    memberCount: number;
 | 
				
			||||||
 | 
					    clanLeaderName: string;
 | 
				
			||||||
 | 
					    clanLeaderId: string;
 | 
				
			||||||
 | 
					}*/
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { Request, Response } from "express";
 | 
					import { Request, Response } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// POST with {} instead of GET as of 38.5.0
 | 
				
			||||||
const getFriendsController = (_request: Request, response: Response): void => {
 | 
					const getFriendsController = (_request: Request, response: Response): void => {
 | 
				
			||||||
    response.writeHead(200, {
 | 
					    response.writeHead(200, {
 | 
				
			||||||
        //Connection: "keep-alive",
 | 
					        //Connection: "keep-alive",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								src/controllers/api/getGuildContributionsController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/controllers/api/getGuildContributionsController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					import { GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IGuildMemberClient } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getGuildContributionsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const guildId = (await getInventory(accountId, "GuildId")).GuildId;
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        _id: { $oid: req.query.buddyId as string },
 | 
				
			||||||
 | 
					        RegularCreditsContributed: guildMember.RegularCreditsContributed,
 | 
				
			||||||
 | 
					        PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
 | 
				
			||||||
 | 
					        MiscItemsContributed: guildMember.MiscItemsContributed,
 | 
				
			||||||
 | 
					        ConsumablesContributed: [], // ???
 | 
				
			||||||
 | 
					        ShipDecorationsContributed: guildMember.ShipDecorationsContributed
 | 
				
			||||||
 | 
					    } satisfies Partial<IGuildMemberClient>);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,73 +1,32 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
					 | 
				
			||||||
import { Guild } from "@/src/models/guildModel";
 | 
					import { Guild } from "@/src/models/guildModel";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getGuildController: RequestHandler = async (req, res) => {
 | 
					export const getGuildController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await Inventory.findOne({ accountOwnerId: accountId });
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
    if (!inventory) {
 | 
					 | 
				
			||||||
        res.status(400).json({ error: "inventory was undefined" });
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (inventory.GuildId) {
 | 
					    if (inventory.GuildId) {
 | 
				
			||||||
        const guild = await Guild.findOne({ _id: inventory.GuildId });
 | 
					        const guild = await Guild.findById(inventory.GuildId);
 | 
				
			||||||
        if (guild) {
 | 
					        if (guild) {
 | 
				
			||||||
            res.json({
 | 
					            // Handle guilds created before we added discriminators
 | 
				
			||||||
                _id: toOid(guild._id),
 | 
					            if (guild.Name.indexOf("#") == -1) {
 | 
				
			||||||
                Name: guild.Name,
 | 
					                guild.Name = await createUniqueClanName(guild.Name);
 | 
				
			||||||
                Members: [
 | 
					                await guild.save();
 | 
				
			||||||
                    {
 | 
					            }
 | 
				
			||||||
                        _id: { $oid: req.query.accountId },
 | 
					
 | 
				
			||||||
                        Rank: 0,
 | 
					            if (guild.CeremonyResetDate && Date.now() >= guild.CeremonyResetDate.getTime()) {
 | 
				
			||||||
                        Status: 0
 | 
					                logger.debug(`ascension ceremony is over`);
 | 
				
			||||||
                    }
 | 
					                guild.CeremonyEndo = undefined;
 | 
				
			||||||
                ],
 | 
					                guild.CeremonyContributors = undefined;
 | 
				
			||||||
                Ranks: [
 | 
					                guild.CeremonyResetDate = undefined;
 | 
				
			||||||
                    {
 | 
					                await guild.save();
 | 
				
			||||||
                        Name: "/Lotus/Language/Game/Rank_Creator",
 | 
					            }
 | 
				
			||||||
                        Permissions: 16351
 | 
					            res.json(await getGuildClient(guild, accountId));
 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        Name: "/Lotus/Language/Game/Rank_Warlord",
 | 
					 | 
				
			||||||
                        Permissions: 14303
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        Name: "/Lotus/Language/Game/Rank_General",
 | 
					 | 
				
			||||||
                        Permissions: 4318
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        Name: "/Lotus/Language/Game/Rank_Officer",
 | 
					 | 
				
			||||||
                        Permissions: 4314
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        Name: "/Lotus/Language/Game/Rank_Leader",
 | 
					 | 
				
			||||||
                        Permissions: 4106
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        Name: "/Lotus/Language/Game/Rank_Sage",
 | 
					 | 
				
			||||||
                        Permissions: 4304
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        Name: "/Lotus/Language/Game/Rank_Soldier",
 | 
					 | 
				
			||||||
                        Permissions: 4098
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        Name: "/Lotus/Language/Game/Rank_Initiate",
 | 
					 | 
				
			||||||
                        Permissions: 4096
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        Name: "/Lotus/Language/Game/Rank_Utility",
 | 
					 | 
				
			||||||
                        Permissions: 4096
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                Tier: 1
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.json({});
 | 
					    res.end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { getGuildController };
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -6,24 +6,28 @@ import { getDojoClient } from "@/src/services/guildService";
 | 
				
			|||||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
 | 
					export const getGuildDojoController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const guildId = req.query.guildId as string;
 | 
					    const guildId = req.query.guildId as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const guild = await Guild.findOne({ _id: guildId });
 | 
					    const guild = await Guild.findById(guildId);
 | 
				
			||||||
    if (!guild) {
 | 
					    if (!guild) {
 | 
				
			||||||
        res.status(404).end();
 | 
					        res.status(404).end();
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Populate dojo info if not present
 | 
					    // Populate dojo info if not present
 | 
				
			||||||
    if (!guild.DojoComponents || guild.DojoComponents.length == 0) {
 | 
					    if (guild.DojoComponents.length == 0) {
 | 
				
			||||||
        guild.DojoComponents = [
 | 
					        guild.DojoComponents.push({
 | 
				
			||||||
            {
 | 
					            _id: new Types.ObjectId(),
 | 
				
			||||||
                _id: new Types.ObjectId(),
 | 
					            pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
 | 
				
			||||||
                pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
 | 
					            ppf: "",
 | 
				
			||||||
                ppf: "",
 | 
					            CompletionTime: new Date(Date.now()),
 | 
				
			||||||
                CompletionTime: new Date(Date.now())
 | 
					            DecoCapacity: 600
 | 
				
			||||||
            }
 | 
					        });
 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
        await guild.save();
 | 
					        await guild.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json(getDojoClient(guild, 0));
 | 
					    const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
 | 
				
			||||||
 | 
					    res.json(await getDojoClient(guild, 0, payload.ComponentId));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IGetGuildDojoRequest {
 | 
				
			||||||
 | 
					    ComponentId?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,60 @@
 | 
				
			|||||||
 | 
					import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { Guild } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IMongoDate } from "@/src/types/commonTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getGuildLogController: RequestHandler = (_req, res) => {
 | 
					export const getGuildLogController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    res.json({
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
        RoomChanges: [],
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
        TechChanges: [],
 | 
					    if (inventory.GuildId) {
 | 
				
			||||||
        RosterActivity: [],
 | 
					        const guild = await Guild.findById(inventory.GuildId);
 | 
				
			||||||
        StandingsUpdates: [],
 | 
					        if (guild) {
 | 
				
			||||||
        ClassChanges: []
 | 
					            const log: Record<string, IGuildLogEntryClient[]> = {
 | 
				
			||||||
    });
 | 
					                RoomChanges: [],
 | 
				
			||||||
 | 
					                TechChanges: [],
 | 
				
			||||||
 | 
					                RosterActivity: [],
 | 
				
			||||||
 | 
					                StandingsUpdates: [],
 | 
				
			||||||
 | 
					                ClassChanges: []
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            guild.RoomChanges?.forEach(entry => {
 | 
				
			||||||
 | 
					                log.RoomChanges.push({
 | 
				
			||||||
 | 
					                    dateTime: toMongoDate(entry.dateTime ?? new Date()),
 | 
				
			||||||
 | 
					                    entryType: entry.entryType,
 | 
				
			||||||
 | 
					                    details: entry.details
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            guild.TechChanges?.forEach(entry => {
 | 
				
			||||||
 | 
					                log.TechChanges.push({
 | 
				
			||||||
 | 
					                    dateTime: toMongoDate(entry.dateTime ?? new Date()),
 | 
				
			||||||
 | 
					                    entryType: entry.entryType,
 | 
				
			||||||
 | 
					                    details: entry.details
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            guild.RosterActivity?.forEach(entry => {
 | 
				
			||||||
 | 
					                log.RosterActivity.push({
 | 
				
			||||||
 | 
					                    dateTime: toMongoDate(entry.dateTime),
 | 
				
			||||||
 | 
					                    entryType: entry.entryType,
 | 
				
			||||||
 | 
					                    details: entry.details
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            guild.ClassChanges?.forEach(entry => {
 | 
				
			||||||
 | 
					                log.ClassChanges.push({
 | 
				
			||||||
 | 
					                    dateTime: toMongoDate(entry.dateTime),
 | 
				
			||||||
 | 
					                    entryType: entry.entryType,
 | 
				
			||||||
 | 
					                    details: entry.details
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            res.json(log);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.sendStatus(200);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IGuildLogEntryClient {
 | 
				
			||||||
 | 
					    dateTime: IMongoDate;
 | 
				
			||||||
 | 
					    entryType: number;
 | 
				
			||||||
 | 
					    details: number | string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,21 @@
 | 
				
			|||||||
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					import { generateRewardSeed } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getNewRewardSeedController: RequestHandler = (_req, res) => {
 | 
					export const getNewRewardSeedController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    res.json({ rewardSeed: generateRewardSeed() });
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function generateRewardSeed(): number {
 | 
					    const rewardSeed = generateRewardSeed();
 | 
				
			||||||
    const min = -Number.MAX_SAFE_INTEGER;
 | 
					    logger.debug(`generated new reward seed: ${rewardSeed}`);
 | 
				
			||||||
    const max = Number.MAX_SAFE_INTEGER;
 | 
					    await Inventory.updateOne(
 | 
				
			||||||
    return Math.floor(Math.random() * (max - min + 1)) + min;
 | 
					        {
 | 
				
			||||||
}
 | 
					            accountOwnerId: accountId
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            RewardSeed: rewardSeed
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    res.json({ rewardSeed: rewardSeed });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										92
									
								
								src/controllers/api/giftingController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/controllers/api/giftingController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
 | 
					import { createMessage } from "@/src/services/inboxService";
 | 
				
			||||||
 | 
					import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					import { IPurchaseParams } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportFlavour } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const giftingController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const data = getJSONfromString<IGiftingRequest>(String(req.body));
 | 
				
			||||||
 | 
					    if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
 | 
				
			||||||
 | 
					        throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const account = await Account.findOne(
 | 
				
			||||||
 | 
					        data.RecipientId ? { _id: data.RecipientId.$oid } : { DisplayName: data.Recipient }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (!account) {
 | 
				
			||||||
 | 
					        res.status(400).send("9").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const inventory = await getInventory(account._id.toString(), "Suits Settings");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Cannot gift items to players that have not completed the tutorial.
 | 
				
			||||||
 | 
					    if (inventory.Suits.length == 0) {
 | 
				
			||||||
 | 
					        res.status(400).send("14").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Cannot gift to players who have gifting disabled.
 | 
				
			||||||
 | 
					    // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
 | 
				
			||||||
 | 
					    if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") {
 | 
				
			||||||
 | 
					        res.status(400).send("17").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Cannot gift items with mastery requirement to players who are too low level. (Code 2)
 | 
				
			||||||
 | 
					    // TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
 | 
				
			||||||
 | 
					    // TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const senderAccount = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const senderInventory = await getInventory(
 | 
				
			||||||
 | 
					        senderAccount._id.toString(),
 | 
				
			||||||
 | 
					        "PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (senderInventory.GiftsRemaining == 0) {
 | 
				
			||||||
 | 
					        res.status(400).send("10").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    senderInventory.GiftsRemaining -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
 | 
				
			||||||
 | 
					    await senderInventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const senderName = getSuffixedName(senderAccount);
 | 
				
			||||||
 | 
					    await createMessage(account._id, [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            sndr: senderName,
 | 
				
			||||||
 | 
					            msg: data.Message || "/Lotus/Language/Menu/GiftReceivedBody_NoCustomMessage",
 | 
				
			||||||
 | 
					            arg: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Key: "GIFTER_NAME",
 | 
				
			||||||
 | 
					                    Tag: senderName
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Key: "GIFT_QUANTITY",
 | 
				
			||||||
 | 
					                    Tag: data.PurchaseParams.Quantity
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            sub: "/Lotus/Language/Menu/GiftReceivedSubject",
 | 
				
			||||||
 | 
					            icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
 | 
				
			||||||
 | 
					            gifts: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    GiftType: data.PurchaseParams.StoreItem
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IGiftingRequest {
 | 
				
			||||||
 | 
					    PurchaseParams: IPurchaseParams;
 | 
				
			||||||
 | 
					    Message?: string;
 | 
				
			||||||
 | 
					    Recipient?: string;
 | 
				
			||||||
 | 
					    RecipientId?: IOid;
 | 
				
			||||||
 | 
					    buildLabel: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,29 +1,29 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { WeaponTypeInternal } from "@/src/services/itemDataService";
 | 
					import { WeaponTypeInternal } from "@/src/services/itemDataService";
 | 
				
			||||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
 | 
					import { ExportRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const modularWeaponCategory: (WeaponTypeInternal | "Hoverboards")[] = [
 | 
					const modularWeaponCategory: (WeaponTypeInternal | "Hoverboards")[] = [
 | 
				
			||||||
    "LongGuns",
 | 
					    "LongGuns",
 | 
				
			||||||
    "Pistols",
 | 
					    "Pistols",
 | 
				
			||||||
    "Melee",
 | 
					    "Melee",
 | 
				
			||||||
    "OperatorAmps",
 | 
					    "OperatorAmps",
 | 
				
			||||||
    "Hoverboards" // Not sure about hoverboards just coppied from modual crafting
 | 
					    "Hoverboards"
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGildWeaponRequest {
 | 
					interface IGildWeaponRequest {
 | 
				
			||||||
    ItemName: string;
 | 
					    ItemName: string;
 | 
				
			||||||
    Recipe: string; // /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
 | 
					    Recipe: string; // e.g. /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
 | 
				
			||||||
    PolarizeSlot?: number;
 | 
					    PolarizeSlot?: number;
 | 
				
			||||||
    PolarizeValue?: ArtifactPolarity;
 | 
					    PolarizeValue?: ArtifactPolarity;
 | 
				
			||||||
    ItemId: string;
 | 
					    ItemId: string;
 | 
				
			||||||
    Category: WeaponTypeInternal | "Hoverboards";
 | 
					    Category: WeaponTypeInternal | "Hoverboards";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// In export there no recipes for gild action, so reputation and ressources only consumed visually
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
					export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
 | 
					    const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
 | 
				
			||||||
@ -40,7 +40,8 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const weapon = inventory[data.Category][weaponIndex];
 | 
					    const weapon = inventory[data.Category][weaponIndex];
 | 
				
			||||||
    weapon.Features = EquipmentFeatures.GILDED; // maybe 9 idk if DOUBLE_CAPACITY is also given
 | 
					    weapon.Features ??= 0;
 | 
				
			||||||
 | 
					    weapon.Features |= EquipmentFeatures.GILDED;
 | 
				
			||||||
    weapon.ItemName = data.ItemName;
 | 
					    weapon.ItemName = data.ItemName;
 | 
				
			||||||
    weapon.XP = 0;
 | 
					    weapon.XP = 0;
 | 
				
			||||||
    if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
 | 
					    if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
 | 
				
			||||||
@ -52,11 +53,29 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    inventory[data.Category][weaponIndex] = weapon;
 | 
					    inventory[data.Category][weaponIndex] = weapon;
 | 
				
			||||||
    await inventory.save();
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					    inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const recipe = ExportRecipes[data.Recipe];
 | 
				
			||||||
 | 
					    inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
 | 
				
			||||||
 | 
					        ItemType: ingredient.ItemType,
 | 
				
			||||||
 | 
					        ItemCount: ingredient.ItemCount * -1
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					    addMiscItems(inventory, inventoryChanges.MiscItems);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const affiliationMods = [];
 | 
				
			||||||
 | 
					    if (recipe.syndicateStandingChange) {
 | 
				
			||||||
 | 
					        const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
 | 
				
			||||||
 | 
					        affiliation.Standing += recipe.syndicateStandingChange.value;
 | 
				
			||||||
 | 
					        affiliationMods.push({
 | 
				
			||||||
 | 
					            Tag: recipe.syndicateStandingChange.tag,
 | 
				
			||||||
 | 
					            Standing: recipe.syndicateStandingChange.value
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        InventoryChanges: {
 | 
					        InventoryChanges: inventoryChanges,
 | 
				
			||||||
            [data.Category]: [weapon]
 | 
					        AffiliationMods: affiliationMods
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -2,8 +2,8 @@ import { RequestHandler } from "express";
 | 
				
			|||||||
import { parseString } from "@/src/helpers/general";
 | 
					import { parseString } from "@/src/helpers/general";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { IGroup } from "@/src/types/loginTypes";
 | 
					 | 
				
			||||||
import { giveKeyChainItem } from "@/src/services/questService";
 | 
					import { giveKeyChainItem } from "@/src/services/questService";
 | 
				
			||||||
 | 
					import { IKeyChainRequest } from "@/src/types/requestTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
 | 
					export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = parseString(req.query.accountId);
 | 
					    const accountId = parseString(req.query.accountId);
 | 
				
			||||||
@ -15,9 +15,3 @@ export const giveKeyChainTriggeredItemsController: RequestHandler = async (req,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    res.send(inventoryChanges);
 | 
					    res.send(inventoryChanges);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface IKeyChainRequest {
 | 
					 | 
				
			||||||
    KeyChain: string;
 | 
					 | 
				
			||||||
    ChainStage: number;
 | 
					 | 
				
			||||||
    Groups?: IGroup[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
 | 
					 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { giveKeyChainMessage } from "@/src/services/questService";
 | 
					import { giveKeyChainMessage } from "@/src/services/questService";
 | 
				
			||||||
 | 
					import { IKeyChainRequest } from "@/src/types/requestTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
 | 
					export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -20,11 +20,11 @@ export const giveQuestKeyRewardController: RequestHandler = async (req, res) =>
 | 
				
			|||||||
    //TODO: consider whishlist changes
 | 
					    //TODO: consider whishlist changes
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IQuestKeyRewardRequest {
 | 
					interface IQuestKeyRewardRequest {
 | 
				
			||||||
    reward: IQuestKeyReward;
 | 
					    reward: IQuestKeyReward;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IQuestKeyReward {
 | 
					interface IQuestKeyReward {
 | 
				
			||||||
    RewardType: string;
 | 
					    RewardType: string;
 | 
				
			||||||
    CouponType: string;
 | 
					    CouponType: string;
 | 
				
			||||||
    Icon: string;
 | 
					    Icon: string;
 | 
				
			||||||
@ -38,7 +38,7 @@ export interface IQuestKeyReward {
 | 
				
			|||||||
    Duration: number;
 | 
					    Duration: number;
 | 
				
			||||||
    CouponSku: number;
 | 
					    CouponSku: number;
 | 
				
			||||||
    Syndicate: string;
 | 
					    Syndicate: string;
 | 
				
			||||||
    Milestones: any[];
 | 
					    //Milestones: any[];
 | 
				
			||||||
    ChooseSetIndex: number;
 | 
					    ChooseSetIndex: number;
 | 
				
			||||||
    NewSystemReward: boolean;
 | 
					    NewSystemReward: boolean;
 | 
				
			||||||
    _id: IOid;
 | 
					    _id: IOid;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,19 +1,8 @@
 | 
				
			|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { InventoryDocumentProps } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { addStartingGear, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
    addEquipment,
 | 
					 | 
				
			||||||
    addItem,
 | 
					 | 
				
			||||||
    combineInventoryChanges,
 | 
					 | 
				
			||||||
    getInventory,
 | 
					 | 
				
			||||||
    updateSlots
 | 
					 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { IInventoryClient, IInventoryDatabase, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { TPartialStartingGear } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { HydratedDocument } from "mongoose";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type TPartialStartingGear = Pick<IInventoryClient, "LongGuns" | "Suits" | "Pistols" | "Melee">;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const giveStartingGearController: RequestHandler = async (req, res) => {
 | 
					export const giveStartingGearController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -25,72 +14,3 @@ export const giveStartingGearController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    res.send(inventoryChanges);
 | 
					    res.send(inventoryChanges);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
//TODO: RawUpgrades might need to return a LastAdded
 | 
					 | 
				
			||||||
const awakeningRewards = [
 | 
					 | 
				
			||||||
    "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
 | 
					 | 
				
			||||||
    "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
 | 
					 | 
				
			||||||
    "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
 | 
					 | 
				
			||||||
    "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
 | 
					 | 
				
			||||||
    "/Lotus/Types/Restoratives/LisetAutoHack",
 | 
					 | 
				
			||||||
    "/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod"
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const addStartingGear = async (
 | 
					 | 
				
			||||||
    inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
 | 
					 | 
				
			||||||
    startingGear: TPartialStartingGear | undefined = undefined
 | 
					 | 
				
			||||||
): Promise<IInventoryChanges> => {
 | 
					 | 
				
			||||||
    const { LongGuns, Pistols, Suits, Melee } = startingGear || {
 | 
					 | 
				
			||||||
        LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
 | 
					 | 
				
			||||||
        Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
 | 
					 | 
				
			||||||
        Suits: [{ ItemType: "/Lotus/Powersuits/Excalibur/Excalibur" }],
 | 
					 | 
				
			||||||
        Melee: [{ ItemType: "/Lotus/Weapons/Tenno/Melee/LongSword/LongSword" }]
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //TODO: properly merge weapon bin changes it is currently static here
 | 
					 | 
				
			||||||
    const inventoryChanges: IInventoryChanges = {};
 | 
					 | 
				
			||||||
    addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, undefined, inventoryChanges);
 | 
					 | 
				
			||||||
    addEquipment(inventory, "Pistols", Pistols[0].ItemType, undefined, inventoryChanges);
 | 
					 | 
				
			||||||
    addEquipment(inventory, "Melee", Melee[0].ItemType, undefined, inventoryChanges);
 | 
					 | 
				
			||||||
    addEquipment(inventory, "Suits", Suits[0].ItemType, undefined, inventoryChanges, { Configs: Suits[0].Configs });
 | 
					 | 
				
			||||||
    addEquipment(
 | 
					 | 
				
			||||||
        inventory,
 | 
					 | 
				
			||||||
        "DataKnives",
 | 
					 | 
				
			||||||
        "/Lotus/Weapons/Tenno/HackingDevices/TnHackingDevice/TnHackingDeviceWeapon",
 | 
					 | 
				
			||||||
        undefined,
 | 
					 | 
				
			||||||
        inventoryChanges,
 | 
					 | 
				
			||||||
        { XP: 450_000 }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    addEquipment(
 | 
					 | 
				
			||||||
        inventory,
 | 
					 | 
				
			||||||
        "Scoops",
 | 
					 | 
				
			||||||
        "/Lotus/Weapons/Tenno/Speedball/SpeedballWeaponTest",
 | 
					 | 
				
			||||||
        undefined,
 | 
					 | 
				
			||||||
        inventoryChanges
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    updateSlots(inventory, InventorySlot.SUITS, 0, 1);
 | 
					 | 
				
			||||||
    updateSlots(inventory, InventorySlot.WEAPONS, 0, 3);
 | 
					 | 
				
			||||||
    inventoryChanges.SuitBin = { count: 1, platinum: 0, Slots: -1 };
 | 
					 | 
				
			||||||
    inventoryChanges.WeaponBin = { count: 3, platinum: 0, Slots: -3 };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await addItem(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain");
 | 
					 | 
				
			||||||
    inventory.ActiveQuest = "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    inventory.PremiumCredits = 50;
 | 
					 | 
				
			||||||
    inventory.PremiumCreditsFree = 50;
 | 
					 | 
				
			||||||
    inventoryChanges.PremiumCredits = 50;
 | 
					 | 
				
			||||||
    inventoryChanges.PremiumCreditsFree = 50;
 | 
					 | 
				
			||||||
    inventory.RegularCredits = 3000;
 | 
					 | 
				
			||||||
    inventoryChanges.RegularCredits = 3000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const item of awakeningRewards) {
 | 
					 | 
				
			||||||
        const inventoryDelta = await addItem(inventory, item);
 | 
					 | 
				
			||||||
        combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    inventory.PlayedParkourTutorial = true;
 | 
					 | 
				
			||||||
    inventory.ReceivedStartingGear = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return inventoryChanges;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,44 +1,139 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getGuildForRequestEx } from "@/src/services/guildService";
 | 
					import {
 | 
				
			||||||
 | 
					    addGuildMemberMiscItemContribution,
 | 
				
			||||||
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
 | 
					    getGuildVault,
 | 
				
			||||||
 | 
					    hasAccessToDojo,
 | 
				
			||||||
 | 
					    hasGuildPermission,
 | 
				
			||||||
 | 
					    processFundedGuildTechProject,
 | 
				
			||||||
 | 
					    processGuildTechProjectContributionsUpdate,
 | 
				
			||||||
 | 
					    removePigmentsFromGuildMembers,
 | 
				
			||||||
 | 
					    scaleRequiredCount,
 | 
				
			||||||
 | 
					    setGuildTechLogState
 | 
				
			||||||
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
					import {
 | 
				
			||||||
 | 
					    addItem,
 | 
				
			||||||
 | 
					    addMiscItems,
 | 
				
			||||||
 | 
					    addRecipes,
 | 
				
			||||||
 | 
					    combineInventoryChanges,
 | 
				
			||||||
 | 
					    getInventory,
 | 
				
			||||||
 | 
					    updateCurrency
 | 
				
			||||||
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const guildTechController: RequestHandler = async (req, res) => {
 | 
					export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
    const data = JSON.parse(String(req.body)) as TGuildTechRequest;
 | 
					    const data = JSON.parse(String(req.body)) as TGuildTechRequest;
 | 
				
			||||||
    const action = data.Action.split(",")[0];
 | 
					    if (data.Action == "Sync") {
 | 
				
			||||||
    if (action == "Sync") {
 | 
					        let needSave = false;
 | 
				
			||||||
        res.json({
 | 
					        const techProjects: ITechProjectClient[] = [];
 | 
				
			||||||
            TechProjects: guild.toJSON().TechProjects
 | 
					        if (guild.TechProjects) {
 | 
				
			||||||
        });
 | 
					            for (const project of guild.TechProjects) {
 | 
				
			||||||
    } else if (action == "Start") {
 | 
					                const techProject: ITechProjectClient = {
 | 
				
			||||||
        const recipe = ExportDojoRecipes.research[data.RecipeType!];
 | 
					                    ItemType: project.ItemType,
 | 
				
			||||||
 | 
					                    ReqCredits: project.ReqCredits,
 | 
				
			||||||
 | 
					                    ReqItems: project.ReqItems,
 | 
				
			||||||
 | 
					                    State: project.State
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                if (project.CompletionDate) {
 | 
				
			||||||
 | 
					                    techProject.CompletionDate = toMongoDate(project.CompletionDate);
 | 
				
			||||||
 | 
					                    if (Date.now() >= project.CompletionDate.getTime()) {
 | 
				
			||||||
 | 
					                        needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                techProjects.push(techProject);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (needSave) {
 | 
				
			||||||
 | 
					            await guild.save();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        res.json({ TechProjects: techProjects });
 | 
				
			||||||
 | 
					    } else if (data.Action == "Start") {
 | 
				
			||||||
 | 
					        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
 | 
					            res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const recipe = ExportDojoRecipes.research[data.RecipeType];
 | 
				
			||||||
        guild.TechProjects ??= [];
 | 
					        guild.TechProjects ??= [];
 | 
				
			||||||
        if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
 | 
					        if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
 | 
				
			||||||
            guild.TechProjects.push({
 | 
					            const techProject =
 | 
				
			||||||
                ItemType: data.RecipeType!,
 | 
					                guild.TechProjects[
 | 
				
			||||||
                ReqCredits: scaleRequiredCount(recipe.price),
 | 
					                    guild.TechProjects.push({
 | 
				
			||||||
                ReqItems: recipe.ingredients.map(x => ({
 | 
					                        ItemType: data.RecipeType,
 | 
				
			||||||
                    ItemType: x.ItemType,
 | 
					                        ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
 | 
				
			||||||
                    ItemCount: scaleRequiredCount(x.ItemCount)
 | 
					                        ReqItems: recipe.ingredients.map(x => ({
 | 
				
			||||||
                })),
 | 
					                            ItemType: x.ItemType,
 | 
				
			||||||
                State: 0
 | 
					                            ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
 | 
				
			||||||
            });
 | 
					                        })),
 | 
				
			||||||
 | 
					                        State: 0
 | 
				
			||||||
 | 
					                    }) - 1
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					            setGuildTechLogState(guild, techProject.ItemType, 5);
 | 
				
			||||||
 | 
					            if (config.noDojoResearchCosts) {
 | 
				
			||||||
 | 
					                processFundedGuildTechProject(guild, techProject, recipe);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
 | 
				
			||||||
 | 
					                    guild.ActiveDojoColorResearch = data.RecipeType;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        await guild.save();
 | 
					        await guild.save();
 | 
				
			||||||
        res.end();
 | 
					        res.end();
 | 
				
			||||||
    } else if (action == "Contribute") {
 | 
					    } else if (data.Action == "Contribute") {
 | 
				
			||||||
        const contributions = data as IGuildTechContributeFields;
 | 
					        if (!hasAccessToDojo(inventory)) {
 | 
				
			||||||
 | 
					            res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const guildMember = (await GuildMember.findOne(
 | 
				
			||||||
 | 
					            { accountId, guildId: guild._id },
 | 
				
			||||||
 | 
					            "RegularCreditsContributed MiscItemsContributed"
 | 
				
			||||||
 | 
					        ))!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const contributions = data;
 | 
				
			||||||
        const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
 | 
					        const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (contributions.VaultCredits) {
 | 
				
			||||||
 | 
					            if (contributions.VaultCredits > techProject.ReqCredits) {
 | 
				
			||||||
 | 
					                contributions.VaultCredits = techProject.ReqCredits;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            techProject.ReqCredits -= contributions.VaultCredits;
 | 
				
			||||||
 | 
					            guild.VaultRegularCredits! -= contributions.VaultCredits;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (contributions.RegularCredits > techProject.ReqCredits) {
 | 
					        if (contributions.RegularCredits > techProject.ReqCredits) {
 | 
				
			||||||
            contributions.RegularCredits = techProject.ReqCredits;
 | 
					            contributions.RegularCredits = techProject.ReqCredits;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        techProject.ReqCredits -= contributions.RegularCredits;
 | 
					        techProject.ReqCredits -= contributions.RegularCredits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guildMember.RegularCreditsContributed ??= 0;
 | 
				
			||||||
 | 
					        guildMember.RegularCreditsContributed += contributions.RegularCredits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (contributions.VaultMiscItems.length) {
 | 
				
			||||||
 | 
					            for (const miscItem of contributions.VaultMiscItems) {
 | 
				
			||||||
 | 
					                const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
 | 
				
			||||||
 | 
					                if (reqItem) {
 | 
				
			||||||
 | 
					                    if (miscItem.ItemCount > reqItem.ItemCount) {
 | 
				
			||||||
 | 
					                        miscItem.ItemCount = reqItem.ItemCount;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    reqItem.ItemCount -= miscItem.ItemCount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
 | 
				
			||||||
 | 
					                    vaultMiscItem.ItemCount -= miscItem.ItemCount;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const miscItemChanges = [];
 | 
					        const miscItemChanges = [];
 | 
				
			||||||
        for (const miscItem of contributions.MiscItems) {
 | 
					        for (const miscItem of contributions.MiscItems) {
 | 
				
			||||||
            const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
 | 
					            const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
 | 
				
			||||||
@ -51,30 +146,29 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                    ItemType: miscItem.ItemType,
 | 
					                    ItemType: miscItem.ItemType,
 | 
				
			||||||
                    ItemCount: miscItem.ItemCount * -1
 | 
					                    ItemCount: miscItem.ItemCount * -1
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                addGuildMemberMiscItemContribution(guildMember, miscItem);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        addMiscItems(inventory, miscItemChanges);
 | 
					        addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
        const inventoryChanges: IInventoryChanges = {
 | 
					        const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
 | 
				
			||||||
            ...updateCurrency(inventory, contributions.RegularCredits, false),
 | 
					        inventoryChanges.MiscItems = miscItemChanges;
 | 
				
			||||||
            MiscItems: miscItemChanges
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
 | 
					        // Check if research is fully funded now.
 | 
				
			||||||
            // This research is now fully funded.
 | 
					        await processGuildTechProjectContributionsUpdate(guild, techProject);
 | 
				
			||||||
            techProject.State = 1;
 | 
					 | 
				
			||||||
            const recipe = ExportDojoRecipes.research[data.RecipeType!];
 | 
					 | 
				
			||||||
            techProject.CompletionDate = new Date(new Date().getTime() + recipe.time * 1000);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await guild.save();
 | 
					        await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
 | 
				
			||||||
        await inventory.save();
 | 
					 | 
				
			||||||
        res.json({
 | 
					        res.json({
 | 
				
			||||||
            InventoryChanges: inventoryChanges
 | 
					            InventoryChanges: inventoryChanges,
 | 
				
			||||||
 | 
					            Vault: getGuildVault(guild)
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    } else if (action == "Buy") {
 | 
					    } else if (data.Action.split(",")[0] == "Buy") {
 | 
				
			||||||
        const purchase = data as IGuildTechBuyFields;
 | 
					        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
 | 
				
			||||||
 | 
					            res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const purchase = data as IGuildTechBuyRequest;
 | 
				
			||||||
        const quantity = parseInt(data.Action.split(",")[1]);
 | 
					        const quantity = parseInt(data.Action.split(",")[1]);
 | 
				
			||||||
        const inventory = await getInventory(accountId);
 | 
					 | 
				
			||||||
        const recipeChanges = [
 | 
					        const recipeChanges = [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                ItemType: purchase.RecipeType,
 | 
					                ItemType: purchase.RecipeType,
 | 
				
			||||||
@ -95,24 +189,68 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                Recipes: recipeChanges
 | 
					                Recipes: recipeChanges
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					    } else if (data.Action == "Fabricate") {
 | 
				
			||||||
 | 
					        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
 | 
				
			||||||
 | 
					            res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const recipe = ExportDojoRecipes.fabrications[data.RecipeType];
 | 
				
			||||||
 | 
					        const inventoryChanges: IInventoryChanges = updateCurrency(inventory, recipe.price, false);
 | 
				
			||||||
 | 
					        inventoryChanges.MiscItems = recipe.ingredients.map(x => ({
 | 
				
			||||||
 | 
					            ItemType: x.ItemType,
 | 
				
			||||||
 | 
					            ItemCount: x.ItemCount * -1
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					        addMiscItems(inventory, inventoryChanges.MiscItems);
 | 
				
			||||||
 | 
					        combineInventoryChanges(inventoryChanges, await addItem(inventory, recipe.resultType));
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        // Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
 | 
				
			||||||
 | 
					        res.json({ inventoryChanges: inventoryChanges });
 | 
				
			||||||
 | 
					    } else if (data.Action == "Pause") {
 | 
				
			||||||
 | 
					        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
 | 
					            res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const project = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
 | 
				
			||||||
 | 
					        project.State = -2;
 | 
				
			||||||
 | 
					        guild.ActiveDojoColorResearch = "";
 | 
				
			||||||
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					        await removePigmentsFromGuildMembers(guild._id);
 | 
				
			||||||
 | 
					        res.end();
 | 
				
			||||||
 | 
					    } else if (data.Action == "Unpause") {
 | 
				
			||||||
 | 
					        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
 | 
					            res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const project = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
 | 
				
			||||||
 | 
					        project.State = 0;
 | 
				
			||||||
 | 
					        guild.ActiveDojoColorResearch = data.RecipeType;
 | 
				
			||||||
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					        res.end();
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 | 
					        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
        throw new Error(`unknown guildTech action: ${data.Action}`);
 | 
					        throw new Error(`unknown guildTech action: ${data.Action}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TGuildTechRequest = {
 | 
					type TGuildTechRequest =
 | 
				
			||||||
    Action: string;
 | 
					    | { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
 | 
				
			||||||
} & Partial<IGuildTechStartFields> &
 | 
					    | IGuildTechBasicRequest
 | 
				
			||||||
    Partial<IGuildTechContributeFields>;
 | 
					    | IGuildTechContributeRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGuildTechStartFields {
 | 
					interface IGuildTechBasicRequest {
 | 
				
			||||||
 | 
					    Action: "Start" | "Fabricate" | "Pause" | "Unpause";
 | 
				
			||||||
    Mode: "Guild";
 | 
					    Mode: "Guild";
 | 
				
			||||||
    RecipeType: string;
 | 
					    RecipeType: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type IGuildTechBuyFields = IGuildTechStartFields;
 | 
					interface IGuildTechBuyRequest {
 | 
				
			||||||
 | 
					    Action: string;
 | 
				
			||||||
 | 
					    Mode: "Guild";
 | 
				
			||||||
 | 
					    RecipeType: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGuildTechContributeFields {
 | 
					interface IGuildTechContributeRequest {
 | 
				
			||||||
 | 
					    Action: "Contribute";
 | 
				
			||||||
    ResearchId: "";
 | 
					    ResearchId: "";
 | 
				
			||||||
    RecipeType: string;
 | 
					    RecipeType: string;
 | 
				
			||||||
    RegularCredits: number;
 | 
					    RegularCredits: number;
 | 
				
			||||||
@ -120,8 +258,3 @@ interface IGuildTechContributeFields {
 | 
				
			|||||||
    VaultCredits: number;
 | 
					    VaultCredits: number;
 | 
				
			||||||
    VaultMiscItems: IMiscItem[];
 | 
					    VaultMiscItems: IMiscItem[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
const scaleRequiredCount = (count: number): number => {
 | 
					 | 
				
			||||||
    // The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
 | 
					 | 
				
			||||||
    return Math.max(1, Math.trunc(count / 100));
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										45
									
								
								src/controllers/api/hubBlessingController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/controllers/api/hubBlessingController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { addBooster, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { getRandomInt } from "@/src/services/rngService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportBoosters } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const hubBlessingController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const data = getJSONfromString<IHubBlessingRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const boosterType = ExportBoosters[data.booster].typeName;
 | 
				
			||||||
 | 
					    if (req.query.mode == "send") {
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId, "BlessingCooldown Boosters");
 | 
				
			||||||
 | 
					        inventory.BlessingCooldown = new Date(Date.now() + 86400000);
 | 
				
			||||||
 | 
					        addBooster(boosterType, 3 * 3600, inventory);
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let token = "";
 | 
				
			||||||
 | 
					        for (let i = 0; i != 32; ++i) {
 | 
				
			||||||
 | 
					            token += getRandomInt(0, 15).toString(16);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            BlessingCooldown: inventory.BlessingCooldown,
 | 
				
			||||||
 | 
					            SendTime: Math.trunc(Date.now() / 1000).toString(),
 | 
				
			||||||
 | 
					            Token: token
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId, "Boosters");
 | 
				
			||||||
 | 
					        addBooster(boosterType, 3 * 3600, inventory);
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            BoosterType: data.booster,
 | 
				
			||||||
 | 
					            Sender: data.senderId
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IHubBlessingRequest {
 | 
				
			||||||
 | 
					    booster: string;
 | 
				
			||||||
 | 
					    senderId?: string; // mode=request
 | 
				
			||||||
 | 
					    sendTime?: string; // mode=request
 | 
				
			||||||
 | 
					    token?: string; // mode=request
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,21 +1,24 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { Inbox } from "@/src/models/inboxModel";
 | 
					import { Inbox } from "@/src/models/inboxModel";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    createMessage,
 | 
				
			||||||
    createNewEventMessages,
 | 
					    createNewEventMessages,
 | 
				
			||||||
    deleteAllMessagesRead,
 | 
					    deleteAllMessagesRead,
 | 
				
			||||||
    deleteMessageRead,
 | 
					    deleteMessageRead,
 | 
				
			||||||
    getAllMessagesSorted,
 | 
					    getAllMessagesSorted,
 | 
				
			||||||
    getMessage
 | 
					    getMessage
 | 
				
			||||||
} from "@/src/services/inboxService";
 | 
					} from "@/src/services/inboxService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
import { addItems, getInventory } from "@/src/services/inventoryService";
 | 
					import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { ExportGear } from "warframe-public-export-plus";
 | 
					import { ExportFlavour, ExportGear } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const inboxController: RequestHandler = async (req, res) => {
 | 
					export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
 | 
					    const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const accountId = account._id.toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (deleteId) {
 | 
					    if (deleteId) {
 | 
				
			||||||
        if (deleteId === "DeleteAllRead") {
 | 
					        if (deleteId === "DeleteAllRead") {
 | 
				
			||||||
@ -29,12 +32,12 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    } else if (messageId) {
 | 
					    } else if (messageId) {
 | 
				
			||||||
        const message = await getMessage(messageId as string);
 | 
					        const message = await getMessage(messageId as string);
 | 
				
			||||||
        message.r = true;
 | 
					        message.r = true;
 | 
				
			||||||
 | 
					        await message.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const attachmentItems = message.att;
 | 
					        const attachmentItems = message.att;
 | 
				
			||||||
        const attachmentCountedItems = message.countedAtt;
 | 
					        const attachmentCountedItems = message.countedAtt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!attachmentItems && !attachmentCountedItems) {
 | 
					        if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
 | 
				
			||||||
            await message.save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            res.status(200).end();
 | 
					            res.status(200).end();
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -54,9 +57,43 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        if (attachmentCountedItems) {
 | 
					        if (attachmentCountedItems) {
 | 
				
			||||||
            await addItems(inventory, attachmentCountedItems, inventoryChanges);
 | 
					            await addItems(inventory, attachmentCountedItems, inventoryChanges);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (message.gifts) {
 | 
				
			||||||
 | 
					            const sender = await getAccountFromSuffixedName(message.sndr);
 | 
				
			||||||
 | 
					            const recipientName = getSuffixedName(account);
 | 
				
			||||||
 | 
					            const giftQuantity = message.arg!.find(x => x.Key == "GIFT_QUANTITY")!.Tag as number;
 | 
				
			||||||
 | 
					            for (const gift of message.gifts) {
 | 
				
			||||||
 | 
					                combineInventoryChanges(
 | 
				
			||||||
 | 
					                    inventoryChanges,
 | 
				
			||||||
 | 
					                    (await handleStoreItemAcquisition(gift.GiftType, inventory, giftQuantity)).InventoryChanges
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if (sender) {
 | 
				
			||||||
 | 
					                    await createMessage(sender._id, [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            sndr: recipientName,
 | 
				
			||||||
 | 
					                            msg: "/Lotus/Language/Menu/GiftReceivedConfirmationBody",
 | 
				
			||||||
 | 
					                            arg: [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    Key: "RECIPIENT_NAME",
 | 
				
			||||||
 | 
					                                    Tag: recipientName
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    Key: "GIFT_TYPE",
 | 
				
			||||||
 | 
					                                    Tag: gift.GiftType
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    Key: "GIFT_QUANTITY",
 | 
				
			||||||
 | 
					                                    Tag: giftQuantity
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ],
 | 
				
			||||||
 | 
					                            sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
 | 
				
			||||||
 | 
					                            icon: ExportFlavour[inventory.ActiveAvatarImageType].icon,
 | 
				
			||||||
 | 
					                            highPriority: true
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        await message.save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        res.json({ InventoryChanges: inventoryChanges });
 | 
					        res.json({ InventoryChanges: inventoryChanges });
 | 
				
			||||||
    } else if (latestClientMessageId) {
 | 
					    } else if (latestClientMessageId) {
 | 
				
			||||||
        await createNewEventMessages(req);
 | 
					        await createNewEventMessages(req);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,24 +1,26 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getInventory, addMiscItems, updateCurrency, addRecipes } from "@/src/services/inventoryService";
 | 
					import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
 | 
				
			||||||
import { IOid } from "@/src/types/commonTypes";
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    IConsumedSuit,
 | 
					    IConsumedSuit,
 | 
				
			||||||
    IHelminthFoodRecord,
 | 
					    IHelminthFoodRecord,
 | 
				
			||||||
    IInfestedFoundryClient,
 | 
					 | 
				
			||||||
    IInfestedFoundryDatabase,
 | 
					 | 
				
			||||||
    IInventoryClient,
 | 
					    IInventoryClient,
 | 
				
			||||||
    IMiscItem,
 | 
					    IMiscItem,
 | 
				
			||||||
    ITypeCount
 | 
					    InventorySlot
 | 
				
			||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					} from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { ExportMisc, ExportRecipes } from "warframe-public-export-plus";
 | 
					import { ExportMisc } from "warframe-public-export-plus";
 | 
				
			||||||
import { getRecipe } from "@/src/services/itemDataService";
 | 
					import { getRecipe } from "@/src/services/itemDataService";
 | 
				
			||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
					 | 
				
			||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
					import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { colorToShard } from "@/src/helpers/shardHelper";
 | 
					import { colorToShard } from "@/src/helpers/shardHelper";
 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    addInfestedFoundryXP,
 | 
				
			||||||
 | 
					    applyCheatsToInfestedFoundry,
 | 
				
			||||||
 | 
					    handleSubsumeCompletion
 | 
				
			||||||
 | 
					} from "@/src/services/infestedFoundryService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
					export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -27,7 +29,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            // shard installation
 | 
					            // shard installation
 | 
				
			||||||
            const request = getJSONfromString<IShardInstallRequest>(String(req.body));
 | 
					            const request = getJSONfromString<IShardInstallRequest>(String(req.body));
 | 
				
			||||||
            const inventory = await getInventory(accountId);
 | 
					            const inventory = await getInventory(accountId);
 | 
				
			||||||
            const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
 | 
					            const suit = inventory.Suits.id(request.SuitId.$oid)!;
 | 
				
			||||||
            if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
 | 
					            if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
 | 
				
			||||||
                suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
 | 
					                suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -55,19 +57,20 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            // shard removal
 | 
					            // shard removal
 | 
				
			||||||
            const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
 | 
					            const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
 | 
				
			||||||
            const inventory = await getInventory(accountId);
 | 
					            const inventory = await getInventory(accountId);
 | 
				
			||||||
            const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
 | 
					            const suit = inventory.Suits.id(request.SuitId.$oid)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // refund shard
 | 
					            const miscItemChanges: IMiscItem[] = [];
 | 
				
			||||||
            const shard = Object.entries(colorToShard).find(
 | 
					            if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
 | 
				
			||||||
                ([color]) => color == suit.ArchonCrystalUpgrades![request.Slot].Color
 | 
					                // refund shard
 | 
				
			||||||
            )![1];
 | 
					                const shard = Object.entries(colorToShard).find(
 | 
				
			||||||
            const miscItemChanges = [
 | 
					                    ([color]) => color == suit.ArchonCrystalUpgrades![request.Slot].Color
 | 
				
			||||||
                {
 | 
					                )![1];
 | 
				
			||||||
 | 
					                miscItemChanges.push({
 | 
				
			||||||
                    ItemType: shard,
 | 
					                    ItemType: shard,
 | 
				
			||||||
                    ItemCount: 1
 | 
					                    ItemCount: 1
 | 
				
			||||||
                }
 | 
					                });
 | 
				
			||||||
            ];
 | 
					                addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
            addMiscItems(inventory, miscItemChanges);
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // remove from suit
 | 
					            // remove from suit
 | 
				
			||||||
            suit.ArchonCrystalUpgrades![request.Slot] = {};
 | 
					            suit.ArchonCrystalUpgrades![request.Slot] = {};
 | 
				
			||||||
@ -126,7 +129,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            const miscItemChanges: IMiscItem[] = [];
 | 
					            const miscItemChanges: IMiscItem[] = [];
 | 
				
			||||||
            let totalPercentagePointsGained = 0;
 | 
					            let totalPercentagePointsGained = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const currentUnixSeconds = Math.trunc(new Date().getTime() / 1000);
 | 
					            const currentUnixSeconds = Math.trunc(Date.now() / 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (const contribution of request.ResourceContributions) {
 | 
					            for (const contribution of request.ResourceContributions) {
 | 
				
			||||||
                const snack = ExportMisc.helminthSnacks[contribution.ItemType];
 | 
					                const snack = ExportMisc.helminthSnacks[contribution.ItemType];
 | 
				
			||||||
@ -258,11 +261,10 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            inventory.InfestedFoundry!.ConsumedSuits ??= [];
 | 
					            inventory.InfestedFoundry!.ConsumedSuits ??= [];
 | 
				
			||||||
            inventory.InfestedFoundry!.ConsumedSuits.push(consumedSuit);
 | 
					            inventory.InfestedFoundry!.ConsumedSuits.push(consumedSuit);
 | 
				
			||||||
            inventory.InfestedFoundry!.LastConsumedSuit = suit;
 | 
					            inventory.InfestedFoundry!.LastConsumedSuit = suit;
 | 
				
			||||||
            inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = new Date(
 | 
					            inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = new Date(Date.now() + 24 * 60 * 60 * 1000);
 | 
				
			||||||
                new Date().getTime() + 24 * 60 * 60 * 1000
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 1600_00);
 | 
					            const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 1600_00);
 | 
				
			||||||
            addRecipes(inventory, recipeChanges);
 | 
					            addRecipes(inventory, recipeChanges);
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.SUITS);
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
 | 
					            const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
 | 
				
			||||||
            applyCheatsToInfestedFoundry(infestedFoundry);
 | 
					            applyCheatsToInfestedFoundry(infestedFoundry);
 | 
				
			||||||
@ -307,7 +309,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
 | 
					            const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
 | 
				
			||||||
            const inventory = await getInventory(accountId);
 | 
					            const inventory = await getInventory(accountId);
 | 
				
			||||||
            const suit = inventory.Suits.id(request.SuitId.$oid)!;
 | 
					            const suit = inventory.Suits.id(request.SuitId.$oid)!;
 | 
				
			||||||
            const upgradesExpiry = new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000);
 | 
					            const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
 | 
				
			||||||
            suit.OffensiveUpgrade = request.OffensiveUpgradeType;
 | 
					            suit.OffensiveUpgrade = request.OffensiveUpgradeType;
 | 
				
			||||||
            suit.DefensiveUpgrade = request.DefensiveUpgradeType;
 | 
					            suit.DefensiveUpgrade = request.DefensiveUpgradeType;
 | 
				
			||||||
            suit.UpgradesExpiry = upgradesExpiry;
 | 
					            suit.UpgradesExpiry = upgradesExpiry;
 | 
				
			||||||
@ -354,6 +356,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
 | 
					            logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
            throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
 | 
					            throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -381,116 +384,11 @@ interface IHelminthFeedRequest {
 | 
				
			|||||||
    }[];
 | 
					    }[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => {
 | 
					 | 
				
			||||||
    const recipeChanges: ITypeCount[] = [];
 | 
					 | 
				
			||||||
    infestedFoundry.XP ??= 0;
 | 
					 | 
				
			||||||
    const prevXP = infestedFoundry.XP;
 | 
					 | 
				
			||||||
    infestedFoundry.XP += delta;
 | 
					 | 
				
			||||||
    if (prevXP < 2250_00 && infestedFoundry.XP >= 2250_00) {
 | 
					 | 
				
			||||||
        infestedFoundry.Slots ??= 0;
 | 
					 | 
				
			||||||
        infestedFoundry.Slots += 3;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 5625_00 && infestedFoundry.XP >= 5625_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({
 | 
					 | 
				
			||||||
            ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldsBlueprint",
 | 
					 | 
				
			||||||
            ItemCount: 1
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 10125_00 && infestedFoundry.XP >= 10125_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthHackBlueprint", ItemCount: 1 });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 15750_00 && infestedFoundry.XP >= 15750_00) {
 | 
					 | 
				
			||||||
        infestedFoundry.Slots ??= 0;
 | 
					 | 
				
			||||||
        infestedFoundry.Slots += 10;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 22500_00 && infestedFoundry.XP >= 22500_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({
 | 
					 | 
				
			||||||
            ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthAmmoEfficiencyBlueprint",
 | 
					 | 
				
			||||||
            ItemCount: 1
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 30375_00 && infestedFoundry.XP >= 30375_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStunBlueprint", ItemCount: 1 });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 39375_00 && infestedFoundry.XP >= 39375_00) {
 | 
					 | 
				
			||||||
        infestedFoundry.Slots ??= 0;
 | 
					 | 
				
			||||||
        infestedFoundry.Slots += 20;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 60750_00 && infestedFoundry.XP >= 60750_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStatusBlueprint", ItemCount: 1 });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 73125_00 && infestedFoundry.XP >= 73125_00) {
 | 
					 | 
				
			||||||
        infestedFoundry.Slots = 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 86625_00 && infestedFoundry.XP >= 86625_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({
 | 
					 | 
				
			||||||
            ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldArmorBlueprint",
 | 
					 | 
				
			||||||
            ItemCount: 1
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 101250_00 && infestedFoundry.XP >= 101250_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({
 | 
					 | 
				
			||||||
            ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthProcBlockBlueprint",
 | 
					 | 
				
			||||||
            ItemCount: 1
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 117000_00 && infestedFoundry.XP >= 117000_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({
 | 
					 | 
				
			||||||
            ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthEnergyShareBlueprint",
 | 
					 | 
				
			||||||
            ItemCount: 1
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 133875_00 && infestedFoundry.XP >= 133875_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({
 | 
					 | 
				
			||||||
            ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthMaxStatusBlueprint",
 | 
					 | 
				
			||||||
            ItemCount: 1
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (prevXP < 151875_00 && infestedFoundry.XP >= 151875_00) {
 | 
					 | 
				
			||||||
        recipeChanges.push({
 | 
					 | 
				
			||||||
            ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthTreasureBlueprint",
 | 
					 | 
				
			||||||
            ItemCount: 1
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return recipeChanges;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IHelminthSubsumeRequest {
 | 
					interface IHelminthSubsumeRequest {
 | 
				
			||||||
    SuitId: IOid;
 | 
					    SuitId: IOid;
 | 
				
			||||||
    Recipe: string;
 | 
					    Recipe: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const handleSubsumeCompletion = (inventory: TInventoryDatabaseDocument): ITypeCount[] => {
 | 
					 | 
				
			||||||
    const [recipeType] = Object.entries(ExportRecipes).find(
 | 
					 | 
				
			||||||
        ([_recipeType, recipe]) =>
 | 
					 | 
				
			||||||
            recipe.secretIngredientAction == "SIA_WARFRAME_ABILITY" &&
 | 
					 | 
				
			||||||
            recipe.secretIngredients![0].ItemType == inventory.InfestedFoundry!.LastConsumedSuit!.ItemType
 | 
					 | 
				
			||||||
    )!;
 | 
					 | 
				
			||||||
    inventory.InfestedFoundry!.LastConsumedSuit = undefined;
 | 
					 | 
				
			||||||
    inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = undefined;
 | 
					 | 
				
			||||||
    const recipeChanges: ITypeCount[] = [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            ItemType: recipeType,
 | 
					 | 
				
			||||||
            ItemCount: 1
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
    addRecipes(inventory, recipeChanges);
 | 
					 | 
				
			||||||
    return recipeChanges;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const applyCheatsToInfestedFoundry = (infestedFoundry: IInfestedFoundryClient): void => {
 | 
					 | 
				
			||||||
    if (config.infiniteHelminthMaterials) {
 | 
					 | 
				
			||||||
        infestedFoundry.Resources = [
 | 
					 | 
				
			||||||
            { ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx", Count: 1000 },
 | 
					 | 
				
			||||||
            { ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics", Count: 1000 },
 | 
					 | 
				
			||||||
            { ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics", Count: 1000 },
 | 
					 | 
				
			||||||
            { ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones", Count: 1000 },
 | 
					 | 
				
			||||||
            { ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBile", Count: 1000 },
 | 
					 | 
				
			||||||
            { ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides", Count: 1000 }
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IHelminthOfferingsUpdate {
 | 
					interface IHelminthOfferingsUpdate {
 | 
				
			||||||
    OfferingsIndex: number;
 | 
					    OfferingsIndex: number;
 | 
				
			||||||
    SuitTypes: string[];
 | 
					    SuitTypes: string[];
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
import allDialogue from "@/static/fixed_responses/allDialogue.json";
 | 
					import allDialogue from "@/static/fixed_responses/allDialogue.json";
 | 
				
			||||||
@ -13,13 +13,15 @@ import {
 | 
				
			|||||||
    ExportResources,
 | 
					    ExportResources,
 | 
				
			||||||
    ExportVirtuals
 | 
					    ExportVirtuals
 | 
				
			||||||
} from "warframe-public-export-plus";
 | 
					} from "warframe-public-export-plus";
 | 
				
			||||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "./infestedFoundryController";
 | 
					import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
 | 
				
			||||||
import { allDailyAffiliationKeys } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { catBreadHash } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const inventoryController: RequestHandler = async (request, response) => {
 | 
					export const inventoryController: RequestHandler = async (request, response) => {
 | 
				
			||||||
    const account = await getAccountForRequest(request);
 | 
					    const accountId = await getAccountIdForRequest(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await Inventory.findOne({ accountOwnerId: account._id.toString() });
 | 
					    const inventory = await Inventory.findOne({ accountOwnerId: accountId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!inventory) {
 | 
					    if (!inventory) {
 | 
				
			||||||
        response.status(400).json({ error: "inventory was undefined" });
 | 
					        response.status(400).json({ error: "inventory was undefined" });
 | 
				
			||||||
@ -27,15 +29,57 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Handle daily reset
 | 
					    // Handle daily reset
 | 
				
			||||||
    const today: number = Math.trunc(new Date().getTime() / 86400000);
 | 
					    if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
 | 
				
			||||||
    if (account.LastLoginDay != today) {
 | 
					 | 
				
			||||||
        account.LastLoginDay = today;
 | 
					 | 
				
			||||||
        await account.save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const key of allDailyAffiliationKeys) {
 | 
					        for (const key of allDailyAffiliationKeys) {
 | 
				
			||||||
            inventory[key] = 16000 + inventory.PlayerLevel * 500;
 | 
					            inventory[key] = 16000 + inventory.PlayerLevel * 500;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
 | 
					        inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
 | 
				
			||||||
 | 
					        inventory.GiftsRemaining = Math.max(8, inventory.PlayerLevel);
 | 
				
			||||||
 | 
					        inventory.TradesRemaining = inventory.PlayerLevel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (inventory.NextRefill) {
 | 
				
			||||||
 | 
					            if (config.noArgonCrystalDecay) {
 | 
				
			||||||
 | 
					                inventory.FoundToday = undefined;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
 | 
				
			||||||
 | 
					                const today = Math.trunc(Date.now() / 86400000);
 | 
				
			||||||
 | 
					                const daysPassed = today - lastLoginDay;
 | 
				
			||||||
 | 
					                for (let i = 0; i != daysPassed; ++i) {
 | 
				
			||||||
 | 
					                    const numArgonCrystals =
 | 
				
			||||||
 | 
					                        inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
 | 
				
			||||||
 | 
					                            ?.ItemCount ?? 0;
 | 
				
			||||||
 | 
					                    if (numArgonCrystals == 0) {
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    const numStableArgonCrystals = Math.min(
 | 
				
			||||||
 | 
					                        numArgonCrystals,
 | 
				
			||||||
 | 
					                        inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
 | 
				
			||||||
 | 
					                            ?.ItemCount ?? 0
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
 | 
				
			||||||
 | 
					                    const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
 | 
				
			||||||
 | 
					                    logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
 | 
				
			||||||
 | 
					                        numArgonCrystals,
 | 
				
			||||||
 | 
					                        numStableArgonCrystals,
 | 
				
			||||||
 | 
					                        numDecayingArgonCrystals,
 | 
				
			||||||
 | 
					                        numDecayingArgonCrystalsToRemove
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    // Remove half of owned decaying argon crystals
 | 
				
			||||||
 | 
					                    addMiscItems(inventory, [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            ItemType: "/Lotus/Types/Items/MiscItems/ArgonCrystal",
 | 
				
			||||||
 | 
					                            ItemCount: numDecayingArgonCrystalsToRemove * -1
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ]);
 | 
				
			||||||
 | 
					                    // All stable argon crystals are now decaying
 | 
				
			||||||
 | 
					                    inventory.FoundToday = undefined;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -67,7 +111,7 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
        inventoryResponse.RegularCredits = 999999999;
 | 
					        inventoryResponse.RegularCredits = 999999999;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (config.infinitePlatinum) {
 | 
					    if (config.infinitePlatinum) {
 | 
				
			||||||
        inventoryResponse.PremiumCreditsFree = 999999999;
 | 
					        inventoryResponse.PremiumCreditsFree = 0;
 | 
				
			||||||
        inventoryResponse.PremiumCredits = 999999999;
 | 
					        inventoryResponse.PremiumCredits = 999999999;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (config.infiniteEndo) {
 | 
					    if (config.infiniteEndo) {
 | 
				
			||||||
@ -207,8 +251,9 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.noDailyStandingLimits) {
 | 
					    if (config.noDailyStandingLimits) {
 | 
				
			||||||
 | 
					        const spoofedDailyAffiliation = Math.max(999_999, 16000 + inventoryResponse.PlayerLevel * 500);
 | 
				
			||||||
        for (const key of allDailyAffiliationKeys) {
 | 
					        for (const key of allDailyAffiliationKeys) {
 | 
				
			||||||
            inventoryResponse[key] = 999_999;
 | 
					            inventoryResponse[key] = spoofedDailyAffiliation;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -216,19 +261,16 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
        applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
 | 
					        applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Fix for #380
 | 
					 | 
				
			||||||
    inventoryResponse.NextRefill = { $date: { $numberLong: "9999999999999" } };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // This determines if the "void fissures" tab is shown in navigation.
 | 
					 | 
				
			||||||
    inventoryResponse.HasOwnedVoidProjectionsPreviously = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
 | 
					    // Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
 | 
				
			||||||
    //inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
 | 
					    //inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set 2FA enabled so trading post can be used
 | 
				
			||||||
 | 
					    inventoryResponse.HWIDProtectEnabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return inventoryResponse;
 | 
					    return inventoryResponse;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addString = (arr: string[], str: string): void => {
 | 
					const addString = (arr: string[], str: string): void => {
 | 
				
			||||||
    if (!arr.find(x => x == str)) {
 | 
					    if (!arr.find(x => x == str)) {
 | 
				
			||||||
        arr.push(str);
 | 
					        arr.push(str);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -255,15 +297,6 @@ const resourceGetParent = (resourceName: string): string | undefined => {
 | 
				
			|||||||
    if (resourceName in ExportResources) {
 | 
					    if (resourceName in ExportResources) {
 | 
				
			||||||
        return ExportResources[resourceName].parentName;
 | 
					        return ExportResources[resourceName].parentName;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
				
			||||||
    return ExportVirtuals[resourceName]?.parentName;
 | 
					    return ExportVirtuals[resourceName]?.parentName;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
// This is FNV1a-32 except operating under modulus 2^31 because JavaScript is stinky and likes producing negative integers out of nowhere.
 | 
					 | 
				
			||||||
const catBreadHash = (name: string): number => {
 | 
					 | 
				
			||||||
    let hash = 2166136261;
 | 
					 | 
				
			||||||
    for (let i = 0; i != name.length; ++i) {
 | 
					 | 
				
			||||||
        hash = (hash ^ name.charCodeAt(i)) & 0x7fffffff;
 | 
					 | 
				
			||||||
        hash = (hash * 16777619) & 0x7fffffff;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return hash;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { updateSlots } from "@/src/services/inventoryService";
 | 
					import { updateSlots } from "@/src/services/inventoryService";
 | 
				
			||||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
    loadout slots are additionally purchased slots only
 | 
					    loadout slots are additionally purchased slots only
 | 
				
			||||||
@ -20,14 +21,20 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const inventorySlotsController: RequestHandler = async (req, res) => {
 | 
					export const inventorySlotsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    //const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
 | 
					    const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //TODO: check which slot was purchased because pvpBonus is also possible
 | 
					    if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) {
 | 
				
			||||||
 | 
					        logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const currencyChanges = updateCurrency(inventory, 20, true);
 | 
					    const currencyChanges = updateCurrency(inventory, 20, true);
 | 
				
			||||||
    updateSlots(inventory, InventorySlot.PVE_LOADOUTS, 1, 1);
 | 
					    updateSlots(inventory, body.Bin, 1, 1);
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({ InventoryChanges: currencyChanges });
 | 
					    res.json({ InventoryChanges: currencyChanges });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IInventorySlotsRequest {
 | 
				
			||||||
 | 
					    Bin: InventorySlot;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,12 +2,13 @@ import { RequestHandler } from "express";
 | 
				
			|||||||
import { getSessionByID } from "@/src/managers/sessionManager";
 | 
					import { getSessionByID } from "@/src/managers/sessionManager";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const joinSessionController: RequestHandler = (_req, res) => {
 | 
					export const joinSessionController: RequestHandler = (req, res) => {
 | 
				
			||||||
    const reqBody = JSON.parse(String(_req.body));
 | 
					    const reqBody = JSON.parse(String(req.body)) as IJoinSessionRequest;
 | 
				
			||||||
    logger.debug(`JoinSession Request`, { reqBody });
 | 
					    logger.debug(`JoinSession Request`, { reqBody });
 | 
				
			||||||
    const req = JSON.parse(String(_req.body));
 | 
					    const session = getSessionByID(reqBody.sessionIds[0]);
 | 
				
			||||||
    const session = getSessionByID(req.sessionIds[0] as string);
 | 
					 | 
				
			||||||
    res.json({ rewardSeed: session?.rewardSeed, sessionId: { $oid: session?.sessionId } });
 | 
					    res.json({ rewardSeed: session?.rewardSeed, sessionId: { $oid: session?.sessionId } });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { joinSessionController };
 | 
					interface IJoinSessionRequest {
 | 
				
			||||||
 | 
					    sessionIds: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -19,10 +19,12 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
				
			|||||||
            ? request.query.buildLabel.split(" ").join("+")
 | 
					            ? request.query.buildLabel.split(" ").join("+")
 | 
				
			||||||
            : buildConfig.buildLabel;
 | 
					            : buildConfig.buildLabel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") {
 | 
					    if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
 | 
					            const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
 | 
				
			||||||
            let name = nameFromEmail;
 | 
					            let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
 | 
				
			||||||
            if (await isNameTaken(name)) {
 | 
					            if (await isNameTaken(name)) {
 | 
				
			||||||
                let suffix = 0;
 | 
					                let suffix = 0;
 | 
				
			||||||
                do {
 | 
					                do {
 | 
				
			||||||
@ -40,11 +42,10 @@ 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(newAccount, buildLabel));
 | 
					            response.json(createLoginResponse(myAddress, newAccount, buildLabel));
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        } catch (error: unknown) {
 | 
					        } catch (error: unknown) {
 | 
				
			||||||
            if (error instanceof Error) {
 | 
					            if (error instanceof Error) {
 | 
				
			||||||
@ -53,24 +54,37 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //email not found or incorrect password
 | 
					    if (!account) {
 | 
				
			||||||
    if (!account || !isCorrectPassword(loginRequest.password, account.password)) {
 | 
					        response.status(400).json({ error: "unknown user" });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isCorrectPassword(loginRequest.password, account.password)) {
 | 
				
			||||||
        response.status(400).json({ error: "incorrect login data" });
 | 
					        response.status(400).json({ error: "incorrect login data" });
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (account.Nonce == 0 || loginRequest.ClientType != "webui") {
 | 
					    if (loginRequest.ClientType == "webui") {
 | 
				
			||||||
 | 
					        if (!account.Nonce) {
 | 
				
			||||||
 | 
					            account.ClientType = "webui";
 | 
				
			||||||
 | 
					            account.Nonce = nonce;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
 | 
				
			||||||
 | 
					            response.status(400).json({ error: "nonce still set" });
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        account.ClientType = loginRequest.ClientType;
 | 
				
			||||||
        account.Nonce = nonce;
 | 
					        account.Nonce = nonce;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (loginRequest.ClientType != "webui") {
 | 
					 | 
				
			||||||
        account.CountryCode = loginRequest.lang.toUpperCase();
 | 
					        account.CountryCode = loginRequest.lang.toUpperCase();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await account.save();
 | 
					    await account.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response.json(createLoginResponse(account.toJSON(), buildLabel));
 | 
					    response.json(createLoginResponse(myAddress, account.toJSON(), buildLabel));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createLoginResponse = (account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
 | 
					const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        id: account.id,
 | 
					        id: account.id,
 | 
				
			||||||
        DisplayName: account.DisplayName,
 | 
					        DisplayName: account.DisplayName,
 | 
				
			||||||
@ -84,9 +98,9 @@ const createLoginResponse = (account: IDatabaseAccountJson, buildLabel: string):
 | 
				
			|||||||
        TrackedSettings: account.TrackedSettings,
 | 
					        TrackedSettings: account.TrackedSettings,
 | 
				
			||||||
        Nonce: account.Nonce,
 | 
					        Nonce: account.Nonce,
 | 
				
			||||||
        Groups: [],
 | 
					        Groups: [],
 | 
				
			||||||
        IRC: config.myIrcAddresses ?? [config.myAddress],
 | 
					        IRC: config.myIrcAddresses ?? [myAddress],
 | 
				
			||||||
        platformCDNs: config.platformCDNs,
 | 
					        platformCDNs: [`https://${myAddress}/`],
 | 
				
			||||||
        HUB: config.hubAddress,
 | 
					        HUB: `https://${myAddress}/api/`,
 | 
				
			||||||
        NRS: config.NRS,
 | 
					        NRS: config.NRS,
 | 
				
			||||||
        DTLS: 99,
 | 
					        DTLS: 99,
 | 
				
			||||||
        BuildLabel: buildLabel,
 | 
					        BuildLabel: buildLabel,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,55 @@
 | 
				
			|||||||
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,
 | 
				
			||||||
 | 
					    isLoginRewardAChoice,
 | 
				
			||||||
 | 
					    setAccountGotLoginRewardToday
 | 
				
			||||||
 | 
					} 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;
 | 
				
			||||||
 | 
					    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) {
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            DailyTributeInfo: {
 | 
				
			||||||
 | 
					                IsMilestoneDay: isMilestoneDay,
 | 
				
			||||||
 | 
					                IsChooseRewardSet: isLoginRewardAChoice(account),
 | 
				
			||||||
 | 
					                LoginDays: account.LoginDays,
 | 
				
			||||||
 | 
					                NextMilestoneReward: "",
 | 
				
			||||||
 | 
					                NextMilestoneDay: nextMilestoneDay
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } satisfies ILoginRewardsReponse);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventory = await getInventory(account._id.toString());
 | 
				
			||||||
 | 
					    const randomRewards = getRandomLoginRewards(account, inventory);
 | 
				
			||||||
 | 
					    const response: ILoginRewardsReponse = {
 | 
				
			||||||
 | 
					        DailyTributeInfo: {
 | 
				
			||||||
 | 
					            Rewards: randomRewards,
 | 
				
			||||||
 | 
					            IsMilestoneDay: isMilestoneDay,
 | 
				
			||||||
 | 
					            IsChooseRewardSet: randomRewards.length != 1,
 | 
				
			||||||
 | 
					            LoginDays: account.LoginDays,
 | 
				
			||||||
 | 
					            NextMilestoneReward: "",
 | 
				
			||||||
 | 
					            NextMilestoneDay: nextMilestoneDay,
 | 
				
			||||||
 | 
					            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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setAccountGotLoginRewardToday(account);
 | 
				
			||||||
 | 
					        await account.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.json(response);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { loginRewardsController };
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										65
									
								
								src/controllers/api/loginRewardsSelectionController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/controllers/api/loginRewardsSelectionController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    claimLoginReward,
 | 
				
			||||||
 | 
					    getRandomLoginRewards,
 | 
				
			||||||
 | 
					    setAccountGotLoginRewardToday
 | 
				
			||||||
 | 
					} 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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setAccountGotLoginRewardToday(account);
 | 
				
			||||||
 | 
					    await account.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,19 +1,28 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					 | 
				
			||||||
import { Account } from "@/src/models/loginModel";
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logoutController: RequestHandler = async (req, res) => {
 | 
					export const logoutController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    if (!req.query.accountId) {
 | 
				
			||||||
    const account = await Account.findOne({ _id: accountId });
 | 
					        throw new Error("Request is missing accountId parameter");
 | 
				
			||||||
    if (account) {
 | 
					 | 
				
			||||||
        account.Nonce = 0;
 | 
					 | 
				
			||||||
        await account.save();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    const nonce: number = parseInt(req.query.nonce as string);
 | 
				
			||||||
 | 
					    if (!nonce) {
 | 
				
			||||||
 | 
					        throw new Error("Request is missing nonce parameter");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await Account.updateOne(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _id: req.query.accountId,
 | 
				
			||||||
 | 
					            Nonce: nonce
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Nonce: 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.writeHead(200, {
 | 
					    res.writeHead(200, {
 | 
				
			||||||
        "Content-Type": "text/html",
 | 
					        "Content-Type": "text/html",
 | 
				
			||||||
        "Content-Length": 1
 | 
					        "Content-Length": 1
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    res.end("1");
 | 
					    res.end("1");
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { logoutController };
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										27
									
								
								src/controllers/api/maturePetController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/controllers/api/maturePetController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const maturePetController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "KubrowPets");
 | 
				
			||||||
 | 
					    const data = getJSONfromString<IMaturePetRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const details = inventory.KubrowPets.id(data.petId)!.Details!;
 | 
				
			||||||
 | 
					    details.IsPuppy = data.revert;
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        petId: data.petId,
 | 
				
			||||||
 | 
					        updateCollar: true,
 | 
				
			||||||
 | 
					        armorSkins: ["", "", ""],
 | 
				
			||||||
 | 
					        furPatterns: data.revert
 | 
				
			||||||
 | 
					            ? ["", "", ""]
 | 
				
			||||||
 | 
					            : [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern],
 | 
				
			||||||
 | 
					        unmature: data.revert
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IMaturePetRequest {
 | 
				
			||||||
 | 
					    petId: string;
 | 
				
			||||||
 | 
					    revert: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -47,14 +47,13 @@ import { logger } from "@/src/utils/logger";
 | 
				
			|||||||
- [ ]  FpsSamples
 | 
					- [ ]  FpsSamples
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
//move credit calc in here, return MissionRewards: [] if no reward info
 | 
					//move credit calc in here, return MissionRewards: [] if no reward info
 | 
				
			||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
					 | 
				
			||||||
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
 | 
					export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
 | 
					    const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
 | 
				
			||||||
    logger.debug("mission report:", missionReport);
 | 
					    logger.debug("mission report:", missionReport);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const inventoryUpdates = addMissionInventoryUpdates(inventory, missionReport);
 | 
					    const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (missionReport.MissionStatus !== "GS_SUCCESS") {
 | 
					    if (missionReport.MissionStatus !== "GS_SUCCESS") {
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,33 +1,24 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    getInventory,
 | 
					    getInventory,
 | 
				
			||||||
    updateCurrency,
 | 
					    updateCurrency,
 | 
				
			||||||
    addEquipment,
 | 
					    addEquipment,
 | 
				
			||||||
    addMiscItems,
 | 
					    addMiscItems,
 | 
				
			||||||
    applyDefaultUpgrades
 | 
					    applyDefaultUpgrades,
 | 
				
			||||||
 | 
					    occupySlot,
 | 
				
			||||||
 | 
					    productCategoryToInventoryBin,
 | 
				
			||||||
 | 
					    combineInventoryChanges,
 | 
				
			||||||
 | 
					    addSpecialItem
 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
import { ExportWeapons } from "warframe-public-export-plus";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { getDefaultUpgrades } from "@/src/services/itemDataService";
 | 
				
			||||||
const modularWeaponTypes: Record<string, TEquipmentKey> = {
 | 
					import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
				
			||||||
    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns",
 | 
					import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": "LongGuns",
 | 
					import { getRandomInt } from "@/src/services/rngService";
 | 
				
			||||||
    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": "LongGuns",
 | 
					import { ExportSentinels } from "warframe-public-export-plus";
 | 
				
			||||||
    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": "LongGuns",
 | 
					import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": "LongGuns",
 | 
					 | 
				
			||||||
    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": "Pistols",
 | 
					 | 
				
			||||||
    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": "Pistols",
 | 
					 | 
				
			||||||
    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": "Pistols",
 | 
					 | 
				
			||||||
    "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": "Melee",
 | 
					 | 
				
			||||||
    "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon": "OperatorAmps",
 | 
					 | 
				
			||||||
    "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": "Hoverboards",
 | 
					 | 
				
			||||||
    "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit": "MoaPets",
 | 
					 | 
				
			||||||
    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit": "MoaPets",
 | 
					 | 
				
			||||||
    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit": "MoaPets",
 | 
					 | 
				
			||||||
    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit": "MoaPets"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IModularCraftRequest {
 | 
					interface IModularCraftRequest {
 | 
				
			||||||
    WeaponType: string;
 | 
					    WeaponType: string;
 | 
				
			||||||
@ -43,11 +34,110 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
				
			|||||||
    const category = modularWeaponTypes[data.WeaponType];
 | 
					    const category = modularWeaponTypes[data.WeaponType];
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
					    const defaultUpgrades = getDefaultUpgrades(data.Parts);
 | 
				
			||||||
    const configs = applyDefaultUpgrades(inventory, ExportWeapons[data.Parts[0]]?.defaultUpgrades);
 | 
					    const defaultOverwrites: Partial<IEquipmentDatabase> = {
 | 
				
			||||||
 | 
					        Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					    if (category == "KubrowPets") {
 | 
				
			||||||
 | 
					        const traits = {
 | 
				
			||||||
 | 
					            "/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit": {
 | 
				
			||||||
 | 
					                BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareBase",
 | 
				
			||||||
 | 
					                SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareSecondary",
 | 
				
			||||||
 | 
					                TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareTertiary",
 | 
				
			||||||
 | 
					                AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareAccent",
 | 
				
			||||||
 | 
					                EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareEyes",
 | 
				
			||||||
 | 
					                FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
 | 
				
			||||||
 | 
					                Personality: data.WeaponType,
 | 
				
			||||||
 | 
					                BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
 | 
				
			||||||
 | 
					                Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadC"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit": {
 | 
				
			||||||
 | 
					                BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonBase",
 | 
				
			||||||
 | 
					                SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonSecondary",
 | 
				
			||||||
 | 
					                TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonTertiary",
 | 
				
			||||||
 | 
					                AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonAccent",
 | 
				
			||||||
 | 
					                EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonEyes",
 | 
				
			||||||
 | 
					                FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
 | 
				
			||||||
 | 
					                Personality: data.WeaponType,
 | 
				
			||||||
 | 
					                BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
 | 
				
			||||||
 | 
					                Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadB"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit": {
 | 
				
			||||||
 | 
					                BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareBase",
 | 
				
			||||||
 | 
					                SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareSecondary",
 | 
				
			||||||
 | 
					                TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareTertiary",
 | 
				
			||||||
 | 
					                AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareAccent",
 | 
				
			||||||
 | 
					                EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareEyes",
 | 
				
			||||||
 | 
					                FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
 | 
				
			||||||
 | 
					                Personality: data.WeaponType,
 | 
				
			||||||
 | 
					                BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
 | 
				
			||||||
 | 
					                Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadA"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit": {
 | 
				
			||||||
 | 
					                BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonBase",
 | 
				
			||||||
 | 
					                SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonSecondary",
 | 
				
			||||||
 | 
					                TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonTertiary",
 | 
				
			||||||
 | 
					                AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonAccent",
 | 
				
			||||||
 | 
					                EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonEyes",
 | 
				
			||||||
 | 
					                FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
 | 
				
			||||||
 | 
					                Personality: data.WeaponType,
 | 
				
			||||||
 | 
					                BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
 | 
				
			||||||
 | 
					                Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadB"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit": {
 | 
				
			||||||
 | 
					                BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonBase",
 | 
				
			||||||
 | 
					                SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonSecondary",
 | 
				
			||||||
 | 
					                TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonTertiary",
 | 
				
			||||||
 | 
					                AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonAccent",
 | 
				
			||||||
 | 
					                EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonEyes",
 | 
				
			||||||
 | 
					                FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
 | 
				
			||||||
 | 
					                Personality: data.WeaponType,
 | 
				
			||||||
 | 
					                BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
 | 
				
			||||||
 | 
					                Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadC"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit": {
 | 
				
			||||||
 | 
					                BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonBase",
 | 
				
			||||||
 | 
					                SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonSecondary",
 | 
				
			||||||
 | 
					                TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonTertiary",
 | 
				
			||||||
 | 
					                AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonAccent",
 | 
				
			||||||
 | 
					                EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonEyes",
 | 
				
			||||||
 | 
					                FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
 | 
				
			||||||
 | 
					                Personality: data.WeaponType,
 | 
				
			||||||
 | 
					                BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
 | 
				
			||||||
 | 
					                Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadA"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }[data.WeaponType];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Give weapon
 | 
					        if (!traits) {
 | 
				
			||||||
    const inventoryChanges = addEquipment(inventory, category, data.WeaponType, data.Parts, {}, { Configs: configs });
 | 
					            throw new Error(`unknown KubrowPets type: ${data.WeaponType}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        defaultOverwrites.Details = {
 | 
				
			||||||
 | 
					            Name: "",
 | 
				
			||||||
 | 
					            IsPuppy: false,
 | 
				
			||||||
 | 
					            HasCollar: true,
 | 
				
			||||||
 | 
					            PrintsRemaining: 2,
 | 
				
			||||||
 | 
					            Status: Status.StatusStasis,
 | 
				
			||||||
 | 
					            HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
 | 
				
			||||||
 | 
					            IsMale: !!getRandomInt(0, 1),
 | 
				
			||||||
 | 
					            Size: getRandomInt(70, 100) / 100,
 | 
				
			||||||
 | 
					            DominantTraits: traits,
 | 
				
			||||||
 | 
					            RecessiveTraits: traits
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Only save mutagen & antigen in the ModularParts.
 | 
				
			||||||
 | 
					        defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const specialItem of ExportSentinels[data.WeaponType].exalted!) {
 | 
				
			||||||
 | 
					            addSpecialItem(inventory, specialItem, inventoryChanges);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
 | 
				
			||||||
 | 
					    combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
 | 
				
			||||||
 | 
					    if (defaultUpgrades) {
 | 
				
			||||||
 | 
					        inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Remove credits & parts
 | 
					    // Remove credits & parts
 | 
				
			||||||
    const miscItemChanges = [];
 | 
					    const miscItemChanges = [];
 | 
				
			||||||
@ -59,7 +149,13 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    const currencyChanges = updateCurrency(
 | 
					    const currencyChanges = updateCurrency(
 | 
				
			||||||
        inventory,
 | 
					        inventory,
 | 
				
			||||||
        category == "Hoverboards" || category == "MoaPets" ? 5000 : 4000,
 | 
					        category == "Hoverboards" ||
 | 
				
			||||||
 | 
					            category == "MoaPets" ||
 | 
				
			||||||
 | 
					            category == "LongGuns" ||
 | 
				
			||||||
 | 
					            category == "Pistols" ||
 | 
				
			||||||
 | 
					            category == "KubrowPets"
 | 
				
			||||||
 | 
					            ? 5000
 | 
				
			||||||
 | 
					            : 4000, // Definitely correct for Melee & OperatorAmps
 | 
				
			||||||
        false
 | 
					        false
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    addMiscItems(inventory, miscItemChanges);
 | 
					    addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,185 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import modularWeaponSale from "@/static/fixed_responses/modularWeaponSale.json";
 | 
					import { ExportWeapons } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { IMongoDate } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { CRng } from "@/src/services/rngService";
 | 
				
			||||||
 | 
					import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    addEquipment,
 | 
				
			||||||
 | 
					    applyDefaultUpgrades,
 | 
				
			||||||
 | 
					    getInventory,
 | 
				
			||||||
 | 
					    occupySlot,
 | 
				
			||||||
 | 
					    productCategoryToInventoryBin,
 | 
				
			||||||
 | 
					    updateCurrency
 | 
				
			||||||
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getDefaultUpgrades } from "@/src/services/itemDataService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const modularWeaponSaleController: RequestHandler = (_req, res) => {
 | 
					export const modularWeaponSaleController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    res.json(modularWeaponSale);
 | 
					    const partTypeToParts: Record<string, string[]> = {};
 | 
				
			||||||
 | 
					    for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
 | 
				
			||||||
 | 
					        if (data.partType && data.premiumPrice) {
 | 
				
			||||||
 | 
					            partTypeToParts[data.partType] ??= [];
 | 
				
			||||||
 | 
					            partTypeToParts[data.partType].push(uniqueName);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (req.query.op == "SyncAll") {
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            SaleInfos: getSaleInfos(partTypeToParts, Math.trunc(Date.now() / 86400000))
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else if (req.query.op == "Purchase") {
 | 
				
			||||||
 | 
					        const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					        const payload = getJSONfromString<IModularWeaponPurchaseRequest>(String(req.body));
 | 
				
			||||||
 | 
					        const weaponInfo = getSaleInfos(partTypeToParts, payload.Revision).find(x => x.Name == payload.SaleName)!
 | 
				
			||||||
 | 
					            .Weapons[payload.ItemIndex];
 | 
				
			||||||
 | 
					        const category = modularWeaponTypes[weaponInfo.ItemType];
 | 
				
			||||||
 | 
					        const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts);
 | 
				
			||||||
 | 
					        const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
 | 
				
			||||||
 | 
					        const inventoryChanges: IInventoryChanges = {
 | 
				
			||||||
 | 
					            ...addEquipment(
 | 
				
			||||||
 | 
					                inventory,
 | 
				
			||||||
 | 
					                category,
 | 
				
			||||||
 | 
					                weaponInfo.ItemType,
 | 
				
			||||||
 | 
					                weaponInfo.ModularParts,
 | 
				
			||||||
 | 
					                {},
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED,
 | 
				
			||||||
 | 
					                    ItemName: payload.ItemName,
 | 
				
			||||||
 | 
					                    Configs: configs,
 | 
				
			||||||
 | 
					                    Polarity: [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Slot: payload.PolarizeSlot,
 | 
				
			||||||
 | 
					                            Value: payload.PolarizeValue
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            ...occupySlot(inventory, productCategoryToInventoryBin(category)!, true),
 | 
				
			||||||
 | 
					            ...updateCurrency(inventory, weaponInfo.PremiumPrice, true)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        if (defaultUpgrades) {
 | 
				
			||||||
 | 
					            inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        throw new Error(`unknown modularWeaponSale op: ${String(req.query.op)}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { modularWeaponSaleController };
 | 
					const getSaleInfos = (partTypeToParts: Record<string, string[]>, day: number): IModularWeaponSaleInfo[] => {
 | 
				
			||||||
 | 
					    const kitgunIsPrimary: boolean = (day & 1) != 0;
 | 
				
			||||||
 | 
					    return [
 | 
				
			||||||
 | 
					        getModularWeaponSale(
 | 
				
			||||||
 | 
					            partTypeToParts,
 | 
				
			||||||
 | 
					            day,
 | 
				
			||||||
 | 
					            "Ostron",
 | 
				
			||||||
 | 
					            ["LWPT_HILT", "LWPT_BLADE", "LWPT_HILT_WEIGHT"],
 | 
				
			||||||
 | 
					            () => "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon"
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        getModularWeaponSale(
 | 
				
			||||||
 | 
					            partTypeToParts,
 | 
				
			||||||
 | 
					            day,
 | 
				
			||||||
 | 
					            "SolarisUnitedHoverboard",
 | 
				
			||||||
 | 
					            ["LWPT_HB_DECK", "LWPT_HB_ENGINE", "LWPT_HB_FRONT", "LWPT_HB_JET"],
 | 
				
			||||||
 | 
					            () => "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit"
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        getModularWeaponSale(
 | 
				
			||||||
 | 
					            partTypeToParts,
 | 
				
			||||||
 | 
					            day,
 | 
				
			||||||
 | 
					            "SolarisUnitedMoaPet",
 | 
				
			||||||
 | 
					            ["LWPT_MOA_LEG", "LWPT_MOA_HEAD", "LWPT_MOA_ENGINE", "LWPT_MOA_PAYLOAD"],
 | 
				
			||||||
 | 
					            () => "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit"
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        getModularWeaponSale(
 | 
				
			||||||
 | 
					            partTypeToParts,
 | 
				
			||||||
 | 
					            day,
 | 
				
			||||||
 | 
					            "SolarisUnitedKitGun",
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                kitgunIsPrimary ? "LWPT_GUN_PRIMARY_HANDLE" : "LWPT_GUN_SECONDARY_HANDLE",
 | 
				
			||||||
 | 
					                "LWPT_GUN_BARREL",
 | 
				
			||||||
 | 
					                "LWPT_GUN_CLIP"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            (parts: string[]) => {
 | 
				
			||||||
 | 
					                const barrel = parts[1];
 | 
				
			||||||
 | 
					                const gunType = ExportWeapons[barrel].gunType!;
 | 
				
			||||||
 | 
					                if (kitgunIsPrimary) {
 | 
				
			||||||
 | 
					                    return {
 | 
				
			||||||
 | 
					                        GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
 | 
				
			||||||
 | 
					                        GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
 | 
				
			||||||
 | 
					                        GT_BEAM: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
 | 
				
			||||||
 | 
					                    }[gunType];
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    return {
 | 
				
			||||||
 | 
					                        GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
 | 
				
			||||||
 | 
					                        GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
 | 
				
			||||||
 | 
					                        GT_BEAM: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
 | 
				
			||||||
 | 
					                    }[gunType];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const priceFactor: Record<string, number> = {
 | 
				
			||||||
 | 
					    Ostron: 0.9,
 | 
				
			||||||
 | 
					    SolarisUnitedHoverboard: 0.85,
 | 
				
			||||||
 | 
					    SolarisUnitedMoaPet: 0.95,
 | 
				
			||||||
 | 
					    SolarisUnitedKitGun: 0.9
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getModularWeaponSale = (
 | 
				
			||||||
 | 
					    partTypeToParts: Record<string, string[]>,
 | 
				
			||||||
 | 
					    day: number,
 | 
				
			||||||
 | 
					    name: string,
 | 
				
			||||||
 | 
					    partTypes: string[],
 | 
				
			||||||
 | 
					    getItemType: (parts: string[]) => string
 | 
				
			||||||
 | 
					): IModularWeaponSaleInfo => {
 | 
				
			||||||
 | 
					    const rng = new CRng(day);
 | 
				
			||||||
 | 
					    const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType]));
 | 
				
			||||||
 | 
					    let partsCost = 0;
 | 
				
			||||||
 | 
					    for (const part of parts) {
 | 
				
			||||||
 | 
					        partsCost += ExportWeapons[part].premiumPrice!;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        Name: name,
 | 
				
			||||||
 | 
					        Expiry: toMongoDate(new Date((day + 1) * 86400000)),
 | 
				
			||||||
 | 
					        Revision: day,
 | 
				
			||||||
 | 
					        Weapons: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ItemType: getItemType(parts),
 | 
				
			||||||
 | 
					                PremiumPrice: Math.trunc(partsCost * priceFactor[name]),
 | 
				
			||||||
 | 
					                ModularParts: parts
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IModularWeaponSaleInfo {
 | 
				
			||||||
 | 
					    Name: string;
 | 
				
			||||||
 | 
					    Expiry: IMongoDate;
 | 
				
			||||||
 | 
					    Revision: number;
 | 
				
			||||||
 | 
					    Weapons: IModularWeaponSaleItem[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IModularWeaponSaleItem {
 | 
				
			||||||
 | 
					    ItemType: string;
 | 
				
			||||||
 | 
					    PremiumPrice: number;
 | 
				
			||||||
 | 
					    ModularParts: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IModularWeaponPurchaseRequest {
 | 
				
			||||||
 | 
					    SaleName: string;
 | 
				
			||||||
 | 
					    ItemIndex: number;
 | 
				
			||||||
 | 
					    Revision: number;
 | 
				
			||||||
 | 
					    ItemName: string;
 | 
				
			||||||
 | 
					    PolarizeSlot: number;
 | 
				
			||||||
 | 
					    PolarizeValue: ArtifactPolarity;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -12,15 +12,17 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const body = getJSONfromString<INameWeaponRequest>(String(req.body));
 | 
					    const body = getJSONfromString<INameWeaponRequest>(String(req.body));
 | 
				
			||||||
    const item = inventory[req.query.Category as string as TEquipmentKey].find(
 | 
					    const item = inventory[req.query.Category as string as TEquipmentKey].id(req.query.ItemId as string)!;
 | 
				
			||||||
        item => item._id.toString() == (req.query.ItemId as string)
 | 
					 | 
				
			||||||
    )!;
 | 
					 | 
				
			||||||
    if (body.ItemName != "") {
 | 
					    if (body.ItemName != "") {
 | 
				
			||||||
        item.ItemName = body.ItemName;
 | 
					        item.ItemName = body.ItemName;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        item.ItemName = undefined;
 | 
					        item.ItemName = undefined;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const currencyChanges = updateCurrency(inventory, "webui" in req.query ? 0 : 15, true);
 | 
					    const currencyChanges = updateCurrency(
 | 
				
			||||||
 | 
					        inventory,
 | 
				
			||||||
 | 
					        req.query.Category == "Horses" || "webui" in req.query ? 0 : 15,
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        InventoryChanges: currencyChanges
 | 
					        InventoryChanges: currencyChanges
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										210
									
								
								src/controllers/api/nemesisController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/controllers/api/nemesisController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,210 @@
 | 
				
			|||||||
 | 
					import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers";
 | 
				
			||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { SRng } from "@/src/services/rngService";
 | 
				
			||||||
 | 
					import { IMongoDate, IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					import { IInnateDamageFingerprint, InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    if ((req.query.mode as string) == "f") {
 | 
				
			||||||
 | 
					        const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId, body.Category + " WeaponBin");
 | 
				
			||||||
 | 
					        const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!;
 | 
				
			||||||
 | 
					        const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!;
 | 
				
			||||||
 | 
					        const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
 | 
				
			||||||
 | 
					        const sourceFingerprint = JSON.parse(sourceWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update destination damage type if desired
 | 
				
			||||||
 | 
					        if (body.UseSourceDmgType) {
 | 
				
			||||||
 | 
					            destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Upgrade destination damage value
 | 
				
			||||||
 | 
					        const destDamage = 0.25 + (destFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
 | 
				
			||||||
 | 
					        const sourceDamage = 0.25 + (sourceFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
 | 
				
			||||||
 | 
					        let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
 | 
				
			||||||
 | 
					        if (newDamage >= 0.5794998) {
 | 
				
			||||||
 | 
					            newDamage = 0.6;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Commit fingerprint
 | 
				
			||||||
 | 
					        destWeapon.UpgradeFingerprint = JSON.stringify(destFingerprint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Remove source weapon
 | 
				
			||||||
 | 
					        inventory[body.Category].pull({ _id: body.SourceWeapon.$oid });
 | 
				
			||||||
 | 
					        freeUpSlot(inventory, InventorySlot.WEAPONS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            InventoryChanges: {
 | 
				
			||||||
 | 
					                [body.Category]: [destWeapon.toJSON()],
 | 
				
			||||||
 | 
					                RemovedIdItems: [{ ItemId: body.SourceWeapon }]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else if ((req.query.mode as string) == "p") {
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId, "Nemesis");
 | 
				
			||||||
 | 
					        const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
 | 
				
			||||||
 | 
					        const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction);
 | 
				
			||||||
 | 
					        let guessResult = 0;
 | 
				
			||||||
 | 
					        if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
 | 
				
			||||||
 | 
					            for (let i = 0; i != 3; ++i) {
 | 
				
			||||||
 | 
					                if (body.guess[i] == passcode[0]) {
 | 
				
			||||||
 | 
					                    guessResult = 1 + i;
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            for (let i = 0; i != 3; ++i) {
 | 
				
			||||||
 | 
					                if (body.guess[i] == passcode[i]) {
 | 
				
			||||||
 | 
					                    ++guessResult;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        res.json({ GuessResult: guessResult });
 | 
				
			||||||
 | 
					    } else if ((req.query.mode as string) == "s") {
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId, "Nemesis");
 | 
				
			||||||
 | 
					        const body = getJSONfromString<INemesisStartRequest>(String(req.body));
 | 
				
			||||||
 | 
					        body.target.fp = BigInt(body.target.fp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let weaponIdx = -1;
 | 
				
			||||||
 | 
					        if (body.target.Faction != "FC_INFESTATION") {
 | 
				
			||||||
 | 
					            let weapons: readonly string[];
 | 
				
			||||||
 | 
					            if (body.target.manifest == "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix") {
 | 
				
			||||||
 | 
					                weapons = kuvaLichVersionSixWeapons;
 | 
				
			||||||
 | 
					            } else if (
 | 
				
			||||||
 | 
					                body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour" ||
 | 
				
			||||||
 | 
					                body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree"
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                weapons = corpusVersionThreeWeapons;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                throw new Error(`unknown nemesis manifest: ${body.target.manifest}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
 | 
				
			||||||
 | 
					            weaponIdx = initialWeaponIdx;
 | 
				
			||||||
 | 
					            do {
 | 
				
			||||||
 | 
					                const weapon = weapons[weaponIdx];
 | 
				
			||||||
 | 
					                if (!body.target.DisallowedWeapons.find(x => x == weapon)) {
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                weaponIdx = (weaponIdx + 1) % weapons.length;
 | 
				
			||||||
 | 
					            } while (weaponIdx != initialWeaponIdx);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inventory.Nemesis = {
 | 
				
			||||||
 | 
					            fp: body.target.fp,
 | 
				
			||||||
 | 
					            manifest: body.target.manifest,
 | 
				
			||||||
 | 
					            KillingSuit: body.target.KillingSuit,
 | 
				
			||||||
 | 
					            killingDamageType: body.target.killingDamageType,
 | 
				
			||||||
 | 
					            ShoulderHelmet: body.target.ShoulderHelmet,
 | 
				
			||||||
 | 
					            WeaponIdx: weaponIdx,
 | 
				
			||||||
 | 
					            AgentIdx: body.target.AgentIdx,
 | 
				
			||||||
 | 
					            BirthNode: body.target.BirthNode,
 | 
				
			||||||
 | 
					            Faction: body.target.Faction,
 | 
				
			||||||
 | 
					            Rank: 0,
 | 
				
			||||||
 | 
					            k: false,
 | 
				
			||||||
 | 
					            Traded: false,
 | 
				
			||||||
 | 
					            d: new Date(),
 | 
				
			||||||
 | 
					            InfNodes: getInfNodes(body.target.Faction, 0),
 | 
				
			||||||
 | 
					            GuessHistory: [],
 | 
				
			||||||
 | 
					            Hints: [],
 | 
				
			||||||
 | 
					            HintProgress: 0,
 | 
				
			||||||
 | 
					            Weakened: body.target.Weakened,
 | 
				
			||||||
 | 
					            PrevOwners: 0,
 | 
				
			||||||
 | 
					            HenchmenKilled: 0,
 | 
				
			||||||
 | 
					            SecondInCommand: body.target.SecondInCommand,
 | 
				
			||||||
 | 
					            MissionCount: 0,
 | 
				
			||||||
 | 
					            LastEnc: 0
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            target: inventory.toJSON().Nemesis
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
 | 
					        throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IValenceFusionRequest {
 | 
				
			||||||
 | 
					    DestWeapon: IOid;
 | 
				
			||||||
 | 
					    SourceWeapon: IOid;
 | 
				
			||||||
 | 
					    Category: TEquipmentKey;
 | 
				
			||||||
 | 
					    UseSourceDmgType: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface INemesisStartRequest {
 | 
				
			||||||
 | 
					    target: {
 | 
				
			||||||
 | 
					        fp: number | bigint;
 | 
				
			||||||
 | 
					        manifest: string;
 | 
				
			||||||
 | 
					        KillingSuit: string;
 | 
				
			||||||
 | 
					        killingDamageType: number;
 | 
				
			||||||
 | 
					        ShoulderHelmet: string;
 | 
				
			||||||
 | 
					        DisallowedWeapons: string[];
 | 
				
			||||||
 | 
					        WeaponIdx: number;
 | 
				
			||||||
 | 
					        AgentIdx: number;
 | 
				
			||||||
 | 
					        BirthNode: string;
 | 
				
			||||||
 | 
					        Faction: string;
 | 
				
			||||||
 | 
					        Rank: number;
 | 
				
			||||||
 | 
					        k: boolean;
 | 
				
			||||||
 | 
					        Traded: boolean;
 | 
				
			||||||
 | 
					        d: IMongoDate;
 | 
				
			||||||
 | 
					        InfNodes: [];
 | 
				
			||||||
 | 
					        GuessHistory: [];
 | 
				
			||||||
 | 
					        Hints: [];
 | 
				
			||||||
 | 
					        HintProgress: number;
 | 
				
			||||||
 | 
					        Weakened: boolean;
 | 
				
			||||||
 | 
					        PrevOwners: number;
 | 
				
			||||||
 | 
					        HenchmenKilled: number;
 | 
				
			||||||
 | 
					        MissionCount?: number; // Added in 38.5.0
 | 
				
			||||||
 | 
					        LastEnc?: number; // Added in 38.5.0
 | 
				
			||||||
 | 
					        SecondInCommand: boolean;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface INemesisPrespawnCheckRequest {
 | 
				
			||||||
 | 
					    guess: number[]; // .length == 3
 | 
				
			||||||
 | 
					    potency?: number[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const kuvaLichVersionSixWeapons = [
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const corpusVersionThreeWeapons = [
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
 | 
				
			||||||
 | 
					    "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
							
								
								
									
										116
									
								
								src/controllers/api/placeDecoInComponentController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/controllers/api/placeDecoInComponentController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    getDojoClient,
 | 
				
			||||||
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
 | 
					    getVaultMiscItemCount,
 | 
				
			||||||
 | 
					    hasAccessToDojo,
 | 
				
			||||||
 | 
					    hasGuildPermission,
 | 
				
			||||||
 | 
					    processDojoBuildMaterialsGathered,
 | 
				
			||||||
 | 
					    scaleRequiredCount
 | 
				
			||||||
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const placeDecoInComponentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId LevelKeys");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
 | 
				
			||||||
 | 
					        res.json({ DojoRequestStatus: -1 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const request = JSON.parse(String(req.body)) as IPlaceDecoInComponentRequest;
 | 
				
			||||||
 | 
					    const component = guild.DojoComponents.id(request.ComponentId)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (component.DecoCapacity === undefined) {
 | 
				
			||||||
 | 
					        component.DecoCapacity = Object.values(ExportDojoRecipes.rooms).find(
 | 
				
			||||||
 | 
					            x => x.resultType == component.pf
 | 
				
			||||||
 | 
					        )!.decoCapacity;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    component.Decos ??= [];
 | 
				
			||||||
 | 
					    if (request.MoveId) {
 | 
				
			||||||
 | 
					        const deco = component.Decos.find(x => x._id.equals(request.MoveId))!;
 | 
				
			||||||
 | 
					        deco.Pos = request.Pos;
 | 
				
			||||||
 | 
					        deco.Rot = request.Rot;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const deco =
 | 
				
			||||||
 | 
					            component.Decos[
 | 
				
			||||||
 | 
					                component.Decos.push({
 | 
				
			||||||
 | 
					                    _id: new Types.ObjectId(),
 | 
				
			||||||
 | 
					                    Type: request.Type,
 | 
				
			||||||
 | 
					                    Pos: request.Pos,
 | 
				
			||||||
 | 
					                    Rot: request.Rot,
 | 
				
			||||||
 | 
					                    Name: request.Name,
 | 
				
			||||||
 | 
					                    Sockets: request.Sockets
 | 
				
			||||||
 | 
					                }) - 1
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					        const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == request.Type);
 | 
				
			||||||
 | 
					        if (meta) {
 | 
				
			||||||
 | 
					            if (meta.capacityCost) {
 | 
				
			||||||
 | 
					                component.DecoCapacity -= meta.capacityCost;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
 | 
				
			||||||
 | 
					            if (deco.Sockets !== undefined) {
 | 
				
			||||||
 | 
					                guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
 | 
				
			||||||
 | 
					                    1;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                guild.VaultShipDecorations!.find(x => x.ItemType == itemType)!.ItemCount -= 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!meta || (meta.price == 0 && meta.ingredients.length == 0)) {
 | 
				
			||||||
 | 
					            deco.CompletionTime = new Date();
 | 
				
			||||||
 | 
					        } else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
 | 
				
			||||||
 | 
					            if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
 | 
				
			||||||
 | 
					                let enoughMiscItems = true;
 | 
				
			||||||
 | 
					                for (const ingredient of meta.ingredients) {
 | 
				
			||||||
 | 
					                    if (
 | 
				
			||||||
 | 
					                        getVaultMiscItemCount(guild, ingredient.ItemType) <
 | 
				
			||||||
 | 
					                        scaleRequiredCount(guild.Tier, ingredient.ItemCount)
 | 
				
			||||||
 | 
					                    ) {
 | 
				
			||||||
 | 
					                        enoughMiscItems = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (enoughMiscItems) {
 | 
				
			||||||
 | 
					                    guild.VaultRegularCredits -= scaleRequiredCount(guild.Tier, meta.price);
 | 
				
			||||||
 | 
					                    deco.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    deco.MiscItems = [];
 | 
				
			||||||
 | 
					                    for (const ingredient of meta.ingredients) {
 | 
				
			||||||
 | 
					                        guild.VaultMiscItems.find(x => x.ItemType == ingredient.ItemType)!.ItemCount -=
 | 
				
			||||||
 | 
					                            scaleRequiredCount(guild.Tier, ingredient.ItemCount);
 | 
				
			||||||
 | 
					                        deco.MiscItems.push({
 | 
				
			||||||
 | 
					                            ItemType: ingredient.ItemType,
 | 
				
			||||||
 | 
					                            ItemCount: scaleRequiredCount(guild.Tier, ingredient.ItemCount)
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    deco.CompletionTime = new Date(Date.now() + meta.time * 1000);
 | 
				
			||||||
 | 
					                    processDojoBuildMaterialsGathered(guild, meta);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.json(await getDojoClient(guild, 0, component._id));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IPlaceDecoInComponentRequest {
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    Revision: number;
 | 
				
			||||||
 | 
					    Type: string;
 | 
				
			||||||
 | 
					    Pos: number[];
 | 
				
			||||||
 | 
					    Rot: number[];
 | 
				
			||||||
 | 
					    Name?: string;
 | 
				
			||||||
 | 
					    Sockets?: number;
 | 
				
			||||||
 | 
					    Scale?: number; // only provided alongside MoveId and seems to always be 1
 | 
				
			||||||
 | 
					    MoveId?: string;
 | 
				
			||||||
 | 
					    ShipDeco?: boolean;
 | 
				
			||||||
 | 
					    VaultDeco?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -6,7 +6,7 @@ import { RequestHandler } from "express";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const playerSkillsController: RequestHandler = async (req, res) => {
 | 
					export const playerSkillsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId, "PlayerSkills");
 | 
				
			||||||
    const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
 | 
					    const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
 | 
					    const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										75
									
								
								src/controllers/api/postGuildAdvertisementController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/controllers/api/postGuildAdvertisementController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { GuildAd, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    addGuildMemberMiscItemContribution,
 | 
				
			||||||
 | 
					    addVaultMiscItems,
 | 
				
			||||||
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
 | 
					    getVaultMiscItemCount,
 | 
				
			||||||
 | 
					    hasGuildPermissionEx
 | 
				
			||||||
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { IPurchaseParams } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const postGuildAdvertisementController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId MiscItems");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }))!;
 | 
				
			||||||
 | 
					    if (!hasGuildPermissionEx(guild, guildMember, GuildPermission.Advertiser)) {
 | 
				
			||||||
 | 
					        res.status(400).end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<IPostGuildAdvertisementRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Handle resource cost
 | 
				
			||||||
 | 
					    const vendor = getVendorManifestByTypeName(
 | 
				
			||||||
 | 
					        "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest"
 | 
				
			||||||
 | 
					    )!;
 | 
				
			||||||
 | 
					    const offer = vendor.VendorInfo.ItemManifest.find(x => x.StoreItem == payload.PurchaseParams.StoreItem)!;
 | 
				
			||||||
 | 
					    if (getVaultMiscItemCount(guild, offer.ItemPrices![0].ItemType) >= offer.ItemPrices![0].ItemCount) {
 | 
				
			||||||
 | 
					        addVaultMiscItems(guild, [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ItemType: offer.ItemPrices![0].ItemType,
 | 
				
			||||||
 | 
					                ItemCount: offer.ItemPrices![0].ItemCount * -1
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const miscItem = inventory.MiscItems.find(x => x.ItemType == offer.ItemPrices![0].ItemType);
 | 
				
			||||||
 | 
					        if (!miscItem || miscItem.ItemCount < offer.ItemPrices![0].ItemCount) {
 | 
				
			||||||
 | 
					            res.status(400).json("Insufficient funds");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        miscItem.ItemCount -= offer.ItemPrices![0].ItemCount;
 | 
				
			||||||
 | 
					        addGuildMemberMiscItemContribution(guildMember, offer.ItemPrices![0]);
 | 
				
			||||||
 | 
					        await guildMember.save();
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create or update ad
 | 
				
			||||||
 | 
					    await GuildAd.findOneAndUpdate(
 | 
				
			||||||
 | 
					        { GuildId: guild._id },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Emblem: guild.Emblem,
 | 
				
			||||||
 | 
					            Expiry: new Date(Date.now() + 12 * 3600 * 1000),
 | 
				
			||||||
 | 
					            Features: payload.Features,
 | 
				
			||||||
 | 
					            GuildName: guild.Name,
 | 
				
			||||||
 | 
					            MemberCount: await GuildMember.countDocuments({ guildId: guild._id, status: 0 }),
 | 
				
			||||||
 | 
					            RecruitMsg: payload.RecruitMsg,
 | 
				
			||||||
 | 
					            Tier: guild.Tier
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        { upsert: true }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IPostGuildAdvertisementRequest {
 | 
				
			||||||
 | 
					    Features: number;
 | 
				
			||||||
 | 
					    RecruitMsg: string;
 | 
				
			||||||
 | 
					    Languages: string[];
 | 
				
			||||||
 | 
					    PurchaseParams: IPurchaseParams;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,19 +1,24 @@
 | 
				
			|||||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => {
 | 
					export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const guild = await getGuildForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const componentId = req.query.componentId as string;
 | 
					    const inventory = await getInventory(accountId, "GuildId LevelKeys");
 | 
				
			||||||
    const component = guild.DojoComponents!.splice(
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
        guild.DojoComponents!.findIndex(x => x._id.toString() === componentId),
 | 
					    if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
 | 
				
			||||||
        1
 | 
					        res.json({ DojoRequestStatus: -1 });
 | 
				
			||||||
    )[0];
 | 
					        return;
 | 
				
			||||||
    const room = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf);
 | 
					 | 
				
			||||||
    if (room) {
 | 
					 | 
				
			||||||
        guild.DojoCapacity -= room.capacity;
 | 
					 | 
				
			||||||
        guild.DojoEnergy -= room.energy;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    const componentId = req.query.componentId as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guild.DojoComponents.id(componentId)!.DestructionTime = new Date(
 | 
				
			||||||
 | 
					        (Math.trunc(Date.now() / 1000) + (config.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
    res.json(getDojoClient(guild, 1));
 | 
					    res.json(await getDojoClient(guild, 0, componentId));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										34
									
								
								src/controllers/api/redeemPromoCodeController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/controllers/api/redeemPromoCodeController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import glyphCodes from "@/static/fixed_responses/glyphsCodes.json";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { addItem, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const redeemPromoCodeController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const body = getJSONfromString<IRedeemPromoCodeRequest>(String(req.body));
 | 
				
			||||||
 | 
					    if (!(body.codeId in glyphCodes)) {
 | 
				
			||||||
 | 
					        res.status(400).send("INVALID_CODE").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "FlavourItems");
 | 
				
			||||||
 | 
					    const acquiredGlyphs: string[] = [];
 | 
				
			||||||
 | 
					    for (const glyph of (glyphCodes as Record<string, string[]>)[body.codeId]) {
 | 
				
			||||||
 | 
					        if (!inventory.FlavourItems.find(x => x.ItemType == glyph)) {
 | 
				
			||||||
 | 
					            acquiredGlyphs.push(glyph);
 | 
				
			||||||
 | 
					            await addItem(inventory, glyph);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (acquiredGlyphs.length == 0) {
 | 
				
			||||||
 | 
					        res.status(400).send("USED_CODE").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        FlavourItems: acquiredGlyphs
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IRedeemPromoCodeRequest {
 | 
				
			||||||
 | 
					    codeId: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								src/controllers/api/releasePetController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/controllers/api/releasePetController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const releasePetController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "RegularCredits KubrowPets");
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<IReleasePetRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventoryChanges = updateCurrency(inventory, 25000, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inventoryChanges.RemovedIdItems = [{ ItemId: { $oid: payload.petId } }];
 | 
				
			||||||
 | 
					    inventory.KubrowPets.pull({ _id: payload.petId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IReleasePetRequest {
 | 
				
			||||||
 | 
					    recipeName: "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe";
 | 
				
			||||||
 | 
					    petId: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								src/controllers/api/removeFromAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/controllers/api/removeFromAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { deleteAlliance } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removeFromAllianceController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    // Check requester is a warlord in their guild
 | 
				
			||||||
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
 | 
				
			||||||
 | 
					    if (guildMember.rank > 1) {
 | 
				
			||||||
 | 
					        res.status(400).json({ Error: 104 });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let allianceMember = (await AllianceMember.findOne({ guildId: guildMember.guildId }))!;
 | 
				
			||||||
 | 
					    if (!guildMember.guildId.equals(req.query.guildId as string)) {
 | 
				
			||||||
 | 
					        // Removing a guild that is not our own needs additional permissions
 | 
				
			||||||
 | 
					        if (!(allianceMember.Permissions & GuildPermission.Ruler)) {
 | 
				
			||||||
 | 
					            res.status(400).json({ Error: 104 });
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update allianceMember to point to the alliance to kick
 | 
				
			||||||
 | 
					        allianceMember = (await AllianceMember.findOne({ guildId: req.query.guildId }))!;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (allianceMember.Permissions & GuildPermission.Ruler) {
 | 
				
			||||||
 | 
					        await deleteAlliance(allianceMember.allianceId);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        await Promise.all([
 | 
				
			||||||
 | 
					            await Guild.updateOne({ _id: allianceMember.guildId }, { $unset: { AllianceId: "" } }),
 | 
				
			||||||
 | 
					            await AllianceMember.deleteOne({ _id: allianceMember._id })
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										88
									
								
								src/controllers/api/removeFromGuildController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/controllers/api/removeFromGuildController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					import { GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { Inbox } from "@/src/models/inboxModel";
 | 
				
			||||||
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
 | 
					import { deleteGuild, getGuildForRequest, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { createMessage } from "@/src/services/inboxService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removeFromGuildController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequest(req);
 | 
				
			||||||
 | 
					    const payload = JSON.parse(String(req.body)) as IRemoveFromGuildRequest;
 | 
				
			||||||
 | 
					    const isKick = !account._id.equals(payload.userId);
 | 
				
			||||||
 | 
					    if (isKick && !(await hasGuildPermission(guild, account._id, GuildPermission.Regulator))) {
 | 
				
			||||||
 | 
					        res.status(400).json("Invalid permission");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ accountId: payload.userId, guildId: guild._id }))!;
 | 
				
			||||||
 | 
					    if (guildMember.rank == 0) {
 | 
				
			||||||
 | 
					        await deleteGuild(guild._id);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        if (guildMember.status == 0) {
 | 
				
			||||||
 | 
					            const inventory = await getInventory(payload.userId, "GuildId LevelKeys Recipes");
 | 
				
			||||||
 | 
					            inventory.GuildId = undefined;
 | 
				
			||||||
 | 
					            removeDojoKeyItems(inventory);
 | 
				
			||||||
 | 
					            await inventory.save();
 | 
				
			||||||
 | 
					        } else if (guildMember.status == 1) {
 | 
				
			||||||
 | 
					            // TOVERIFY: Is this inbox message actually sent on live?
 | 
				
			||||||
 | 
					            await createMessage(guildMember.accountId, [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    sndr: "/Lotus/Language/Bosses/Ordis",
 | 
				
			||||||
 | 
					                    msg: "/Lotus/Language/Clan/RejectedFromClan",
 | 
				
			||||||
 | 
					                    sub: "/Lotus/Language/Clan/RejectedFromClanHeader",
 | 
				
			||||||
 | 
					                    arg: [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Key: "PLAYER_NAME",
 | 
				
			||||||
 | 
					                            Tag: (await Account.findOne({ _id: guildMember.accountId }, "DisplayName"))!.DisplayName
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Key: "CLAN_NAME",
 | 
				
			||||||
 | 
					                            Tag: guild.Name
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                    // TOVERIFY: If this message is sent on live, is it highPriority?
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					        } else if (guildMember.status == 2) {
 | 
				
			||||||
 | 
					            // Delete the inbox message for the invite
 | 
				
			||||||
 | 
					            await Inbox.deleteOne({
 | 
				
			||||||
 | 
					                ownerId: guildMember.accountId,
 | 
				
			||||||
 | 
					                contextInfo: guild._id.toString(),
 | 
				
			||||||
 | 
					                acceptAction: "GUILD_INVITE"
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await GuildMember.deleteOne({ _id: guildMember._id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guild.RosterActivity ??= [];
 | 
				
			||||||
 | 
					        if (isKick) {
 | 
				
			||||||
 | 
					            const kickee = (await Account.findById(payload.userId))!;
 | 
				
			||||||
 | 
					            guild.RosterActivity.push({
 | 
				
			||||||
 | 
					                dateTime: new Date(),
 | 
				
			||||||
 | 
					                entryType: 12,
 | 
				
			||||||
 | 
					                details: getSuffixedName(kickee) + "," + getSuffixedName(account)
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            guild.RosterActivity.push({
 | 
				
			||||||
 | 
					                dateTime: new Date(),
 | 
				
			||||||
 | 
					                entryType: 7,
 | 
				
			||||||
 | 
					                details: getSuffixedName(account)
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        _id: payload.userId,
 | 
				
			||||||
 | 
					        ItemToRemove: "/Lotus/Types/Keys/DojoKey",
 | 
				
			||||||
 | 
					        RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IRemoveFromGuildRequest {
 | 
				
			||||||
 | 
					    userId: string;
 | 
				
			||||||
 | 
					    kicker?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,43 +2,54 @@ import { RequestHandler } from "express";
 | 
				
			|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { IUnveiledRivenFingerprint, randomiseRivenStats } from "@/src/helpers/rivenFingerprintHelper";
 | 
					import { createUnveiledRivenFingerprint, randomiseRivenStats, RivenFingerprint } from "@/src/helpers/rivenHelper";
 | 
				
			||||||
import { ExportUpgrades } from "warframe-public-export-plus";
 | 
					import { ExportUpgrades } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const rerollRandomModController: RequestHandler = async (req, res) => {
 | 
					export const rerollRandomModController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const request = getJSONfromString<RerollRandomModRequest>(String(req.body));
 | 
					    const request = getJSONfromString<RerollRandomModRequest>(String(req.body));
 | 
				
			||||||
    if ("ItemIds" in request) {
 | 
					    if ("ItemIds" in request) {
 | 
				
			||||||
        const inventory = await getInventory(accountId, "Upgrades MiscItems");
 | 
					        const inventory = await getInventory(accountId, "Upgrades MiscItems");
 | 
				
			||||||
        const upgrade = inventory.Upgrades.id(request.ItemIds[0])!;
 | 
					        const changes: IChange[] = [];
 | 
				
			||||||
        const fingerprint = JSON.parse(upgrade.UpgradeFingerprint!) as IUnveiledRivenFingerprint;
 | 
					        let totalKuvaCost = 0;
 | 
				
			||||||
 | 
					        request.ItemIds.forEach(itemId => {
 | 
				
			||||||
 | 
					            const upgrade = inventory.Upgrades.id(itemId)!;
 | 
				
			||||||
 | 
					            const fingerprint = JSON.parse(upgrade.UpgradeFingerprint!) as RivenFingerprint;
 | 
				
			||||||
 | 
					            if ("challenge" in fingerprint) {
 | 
				
			||||||
 | 
					                upgrade.UpgradeFingerprint = JSON.stringify(
 | 
				
			||||||
 | 
					                    createUnveiledRivenFingerprint(ExportUpgrades[upgrade.ItemType])
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                fingerprint.rerolls ??= 0;
 | 
				
			||||||
 | 
					                const kuvaCost = fingerprint.rerolls < rerollCosts.length ? rerollCosts[fingerprint.rerolls] : 3500;
 | 
				
			||||||
 | 
					                totalKuvaCost += kuvaCost;
 | 
				
			||||||
 | 
					                addMiscItems(inventory, [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        ItemType: "/Lotus/Types/Items/MiscItems/Kuva",
 | 
				
			||||||
 | 
					                        ItemCount: kuvaCost * -1
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fingerprint.rerolls ??= 0;
 | 
					                fingerprint.rerolls++;
 | 
				
			||||||
        const kuvaCost = fingerprint.rerolls < rerollCosts.length ? rerollCosts[fingerprint.rerolls] : 3500;
 | 
					                upgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
 | 
				
			||||||
        addMiscItems(inventory, [
 | 
					
 | 
				
			||||||
            {
 | 
					                randomiseRivenStats(ExportUpgrades[upgrade.ItemType], fingerprint);
 | 
				
			||||||
                ItemType: "/Lotus/Types/Items/MiscItems/Kuva",
 | 
					                upgrade.PendingRerollFingerprint = JSON.stringify(fingerprint);
 | 
				
			||||||
                ItemCount: kuvaCost * -1
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fingerprint.rerolls++;
 | 
					            changes.push({
 | 
				
			||||||
        upgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
 | 
					                ItemId: { $oid: request.ItemIds[0] },
 | 
				
			||||||
 | 
					                UpgradeFingerprint: upgrade.UpgradeFingerprint,
 | 
				
			||||||
        randomiseRivenStats(ExportUpgrades[upgrade.ItemType], fingerprint);
 | 
					                PendingRerollFingerprint: upgrade.PendingRerollFingerprint
 | 
				
			||||||
        upgrade.PendingRerollFingerprint = JSON.stringify(fingerprint);
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res.json({
 | 
					        res.json({
 | 
				
			||||||
            changes: [
 | 
					            changes: changes,
 | 
				
			||||||
                {
 | 
					            cost: totalKuvaCost
 | 
				
			||||||
                    ItemId: { $oid: request.ItemIds[0] },
 | 
					 | 
				
			||||||
                    UpgradeFingerprint: upgrade.UpgradeFingerprint,
 | 
					 | 
				
			||||||
                    PendingRerollFingerprint: upgrade.PendingRerollFingerprint
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            cost: kuvaCost
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        const inventory = await getInventory(accountId, "Upgrades");
 | 
					        const inventory = await getInventory(accountId, "Upgrades");
 | 
				
			||||||
@ -63,4 +74,10 @@ interface AwDangitRequest {
 | 
				
			|||||||
    CommitReroll: boolean;
 | 
					    CommitReroll: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IChange {
 | 
				
			||||||
 | 
					    ItemId: IOid;
 | 
				
			||||||
 | 
					    UpgradeFingerprint?: string;
 | 
				
			||||||
 | 
					    PendingRerollFingerprint?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rerollCosts = [900, 1000, 1200, 1400, 1700, 2000, 2350, 2750, 3150];
 | 
					const rerollCosts = [900, 1000, 1200, 1400, 1700, 2000, 2350, 2750, 3150];
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										33
									
								
								src/controllers/api/retrievePetFromStasisController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/controllers/api/retrievePetFromStasisController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const retrievePetFromStasisController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "KubrowPets");
 | 
				
			||||||
 | 
					    const data = getJSONfromString<IRetrievePetFromStasisRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let oldPetId: string | undefined;
 | 
				
			||||||
 | 
					    for (const pet of inventory.KubrowPets) {
 | 
				
			||||||
 | 
					        if (pet.Details!.Status == Status.StatusAvailable) {
 | 
				
			||||||
 | 
					            pet.Details!.Status = Status.StatusStasis;
 | 
				
			||||||
 | 
					            oldPetId = pet._id.toString();
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inventory.KubrowPets.id(data.petId)!.Details!.Status = Status.StatusAvailable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        petId: data.petId,
 | 
				
			||||||
 | 
					        oldPetId,
 | 
				
			||||||
 | 
					        status: Status.StatusAvailable
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IRetrievePetFromStasisRequest {
 | 
				
			||||||
 | 
					    petId: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -81,5 +81,11 @@ interface SaveCompletedDialogueRequest {
 | 
				
			|||||||
    Booleans: string[];
 | 
					    Booleans: string[];
 | 
				
			||||||
    ResetBooleans: string[];
 | 
					    ResetBooleans: string[];
 | 
				
			||||||
    Data: ICompletedDialogue;
 | 
					    Data: ICompletedDialogue;
 | 
				
			||||||
    OtherDialogueInfos: string[]; // unsure
 | 
					    OtherDialogueInfos: IOtherDialogueInfo[]; // unsure
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IOtherDialogueInfo {
 | 
				
			||||||
 | 
					    Dialogue: string;
 | 
				
			||||||
 | 
					    Tag: string;
 | 
				
			||||||
 | 
					    Value: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,10 +13,10 @@ const saveSettingsController: RequestHandler = async (req, res): Promise<void> =
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const settingResults = getJSONfromString<ISaveSettingsRequest>(String(req.body));
 | 
					    const settingResults = getJSONfromString<ISaveSettingsRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId, "Settings");
 | 
				
			||||||
    inventory.Settings = Object.assign(inventory.Settings, settingResults.Settings);
 | 
					    inventory.Settings = Object.assign(inventory.Settings ?? {}, settingResults.Settings);
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json(inventory.Settings);
 | 
					    res.json({ Settings: inventory.Settings });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { saveSettingsController };
 | 
					export { saveSettingsController };
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										25
									
								
								src/controllers/api/saveVaultAutoContributeController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/controllers/api/saveVaultAutoContributeController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Guild } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { hasGuildPermission } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const saveVaultAutoContributeController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
 | 
					    const guild = (await Guild.findById(inventory.GuildId!, "Ranks AutoContributeFromVault"))!;
 | 
				
			||||||
 | 
					    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Treasurer))) {
 | 
				
			||||||
 | 
					        res.status(400).send("Invalid permission").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const data = getJSONfromString<ISetVaultAutoContributeRequest>(String(req.body));
 | 
				
			||||||
 | 
					    guild.AutoContributeFromVault = data.autoContributeFromVault;
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISetVaultAutoContributeRequest {
 | 
				
			||||||
 | 
					    autoContributeFromVault: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,11 +1,54 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getInventory, addMods, addRecipes, addMiscItems, addConsumables } from "@/src/services/inventoryService";
 | 
					import {
 | 
				
			||||||
 | 
					    getInventory,
 | 
				
			||||||
 | 
					    addMods,
 | 
				
			||||||
 | 
					    addRecipes,
 | 
				
			||||||
 | 
					    addMiscItems,
 | 
				
			||||||
 | 
					    addConsumables,
 | 
				
			||||||
 | 
					    freeUpSlot
 | 
				
			||||||
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const sellController: RequestHandler = async (req, res) => {
 | 
					export const sellController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const payload = JSON.parse(String(req.body)) as ISellRequest;
 | 
					    const payload = JSON.parse(String(req.body)) as ISellRequest;
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const requiredFields = new Set();
 | 
				
			||||||
 | 
					    if (payload.SellCurrency == "SC_RegularCredits") {
 | 
				
			||||||
 | 
					        requiredFields.add("RegularCredits");
 | 
				
			||||||
 | 
					    } else if (payload.SellCurrency == "SC_FusionPoints") {
 | 
				
			||||||
 | 
					        requiredFields.add("FusionPoints");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        requiredFields.add("MiscItems");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (const key of Object.keys(payload.Items)) {
 | 
				
			||||||
 | 
					        requiredFields.add(key);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (requiredFields.has("Upgrades")) {
 | 
				
			||||||
 | 
					        requiredFields.add("RawUpgrades");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.Suits) {
 | 
				
			||||||
 | 
					        requiredFields.add(InventorySlot.SUITS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.LongGuns || payload.Items.Pistols || payload.Items.Melee) {
 | 
				
			||||||
 | 
					        requiredFields.add(InventorySlot.WEAPONS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.SpaceSuits) {
 | 
				
			||||||
 | 
					        requiredFields.add(InventorySlot.SPACESUITS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
 | 
				
			||||||
 | 
					        requiredFields.add(InventorySlot.SPACEWEAPONS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.Sentinels || payload.Items.SentinelWeapons) {
 | 
				
			||||||
 | 
					        requiredFields.add(InventorySlot.SENTINELS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.OperatorAmps) {
 | 
				
			||||||
 | 
					        requiredFields.add(InventorySlot.AMPS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.Hoverboards) {
 | 
				
			||||||
 | 
					        requiredFields.add(InventorySlot.SPACESUITS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Give currency
 | 
					    // Give currency
 | 
				
			||||||
    if (payload.SellCurrency == "SC_RegularCredits") {
 | 
					    if (payload.SellCurrency == "SC_RegularCredits") {
 | 
				
			||||||
@ -34,56 +77,72 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    if (payload.Items.Suits) {
 | 
					    if (payload.Items.Suits) {
 | 
				
			||||||
        payload.Items.Suits.forEach(sellItem => {
 | 
					        payload.Items.Suits.forEach(sellItem => {
 | 
				
			||||||
            inventory.Suits.pull({ _id: sellItem.String });
 | 
					            inventory.Suits.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.SUITS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.LongGuns) {
 | 
					    if (payload.Items.LongGuns) {
 | 
				
			||||||
        payload.Items.LongGuns.forEach(sellItem => {
 | 
					        payload.Items.LongGuns.forEach(sellItem => {
 | 
				
			||||||
            inventory.LongGuns.pull({ _id: sellItem.String });
 | 
					            inventory.LongGuns.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.WEAPONS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.Pistols) {
 | 
					    if (payload.Items.Pistols) {
 | 
				
			||||||
        payload.Items.Pistols.forEach(sellItem => {
 | 
					        payload.Items.Pistols.forEach(sellItem => {
 | 
				
			||||||
            inventory.Pistols.pull({ _id: sellItem.String });
 | 
					            inventory.Pistols.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.WEAPONS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.Melee) {
 | 
					    if (payload.Items.Melee) {
 | 
				
			||||||
        payload.Items.Melee.forEach(sellItem => {
 | 
					        payload.Items.Melee.forEach(sellItem => {
 | 
				
			||||||
            inventory.Melee.pull({ _id: sellItem.String });
 | 
					            inventory.Melee.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.WEAPONS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.SpaceSuits) {
 | 
					    if (payload.Items.SpaceSuits) {
 | 
				
			||||||
        payload.Items.SpaceSuits.forEach(sellItem => {
 | 
					        payload.Items.SpaceSuits.forEach(sellItem => {
 | 
				
			||||||
            inventory.SpaceSuits.pull({ _id: sellItem.String });
 | 
					            inventory.SpaceSuits.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.SPACESUITS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.SpaceGuns) {
 | 
					    if (payload.Items.SpaceGuns) {
 | 
				
			||||||
        payload.Items.SpaceGuns.forEach(sellItem => {
 | 
					        payload.Items.SpaceGuns.forEach(sellItem => {
 | 
				
			||||||
            inventory.SpaceGuns.pull({ _id: sellItem.String });
 | 
					            inventory.SpaceGuns.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.SpaceMelee) {
 | 
					    if (payload.Items.SpaceMelee) {
 | 
				
			||||||
        payload.Items.SpaceMelee.forEach(sellItem => {
 | 
					        payload.Items.SpaceMelee.forEach(sellItem => {
 | 
				
			||||||
            inventory.SpaceMelee.pull({ _id: sellItem.String });
 | 
					            inventory.SpaceMelee.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.Sentinels) {
 | 
					    if (payload.Items.Sentinels) {
 | 
				
			||||||
        payload.Items.Sentinels.forEach(sellItem => {
 | 
					        payload.Items.Sentinels.forEach(sellItem => {
 | 
				
			||||||
            inventory.Sentinels.pull({ _id: sellItem.String });
 | 
					            inventory.Sentinels.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.SENTINELS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.SentinelWeapons) {
 | 
					    if (payload.Items.SentinelWeapons) {
 | 
				
			||||||
        payload.Items.SentinelWeapons.forEach(sellItem => {
 | 
					        payload.Items.SentinelWeapons.forEach(sellItem => {
 | 
				
			||||||
            inventory.SentinelWeapons.pull({ _id: sellItem.String });
 | 
					            inventory.SentinelWeapons.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.SENTINELS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.OperatorAmps) {
 | 
					    if (payload.Items.OperatorAmps) {
 | 
				
			||||||
        payload.Items.OperatorAmps.forEach(sellItem => {
 | 
					        payload.Items.OperatorAmps.forEach(sellItem => {
 | 
				
			||||||
            inventory.OperatorAmps.pull({ _id: sellItem.String });
 | 
					            inventory.OperatorAmps.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.AMPS);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.Hoverboards) {
 | 
					    if (payload.Items.Hoverboards) {
 | 
				
			||||||
        payload.Items.Hoverboards.forEach(sellItem => {
 | 
					        payload.Items.Hoverboards.forEach(sellItem => {
 | 
				
			||||||
            inventory.Hoverboards.pull({ _id: sellItem.String });
 | 
					            inventory.Hoverboards.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.SPACESUITS);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.Drones) {
 | 
				
			||||||
 | 
					        payload.Items.Drones.forEach(sellItem => {
 | 
				
			||||||
 | 
					            inventory.Drones.pull({ _id: sellItem.String });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (payload.Items.Consumables) {
 | 
					    if (payload.Items.Consumables) {
 | 
				
			||||||
@ -152,6 +211,7 @@ interface ISellRequest {
 | 
				
			|||||||
        SentinelWeapons?: ISellItem[];
 | 
					        SentinelWeapons?: ISellItem[];
 | 
				
			||||||
        OperatorAmps?: ISellItem[];
 | 
					        OperatorAmps?: ISellItem[];
 | 
				
			||||||
        Hoverboards?: ISellItem[];
 | 
					        Hoverboards?: ISellItem[];
 | 
				
			||||||
 | 
					        Drones?: ISellItem[];
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    SellPrice: number;
 | 
					    SellPrice: number;
 | 
				
			||||||
    SellCurrency:
 | 
					    SellCurrency:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										38
									
								
								src/controllers/api/setAllianceGuildPermissionsController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/controllers/api/setAllianceGuildPermissionsController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import { AllianceMember, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setAllianceGuildPermissionsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    // Check requester is a warlord in their guild
 | 
				
			||||||
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
 | 
				
			||||||
 | 
					    if (guildMember.rank > 1) {
 | 
				
			||||||
 | 
					        res.status(400).end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check guild is the creator of the alliance and don't allow changing of own permissions. (Technically changing permissions requires the Promoter permission, but both are exclusive to the creator guild.)
 | 
				
			||||||
 | 
					    const allianceMember = (await AllianceMember.findOne({
 | 
				
			||||||
 | 
					        guildId: guildMember.guildId,
 | 
				
			||||||
 | 
					        Pending: false
 | 
				
			||||||
 | 
					    }))!;
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        !(allianceMember.Permissions & GuildPermission.Ruler) ||
 | 
				
			||||||
 | 
					        allianceMember.guildId.equals(req.query.guildId as string)
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        res.status(400).end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const targetAllianceMember = (await AllianceMember.findOne({
 | 
				
			||||||
 | 
					        allianceId: allianceMember.allianceId,
 | 
				
			||||||
 | 
					        guildId: req.query.guildId
 | 
				
			||||||
 | 
					    }))!;
 | 
				
			||||||
 | 
					    targetAllianceMember.Permissions =
 | 
				
			||||||
 | 
					        parseInt(req.query.perms as string) &
 | 
				
			||||||
 | 
					        (GuildPermission.Recruiter | GuildPermission.Treasurer | GuildPermission.ChatModerator);
 | 
				
			||||||
 | 
					    await targetAllianceMember.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -4,7 +4,7 @@ import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
				
			|||||||
export const setDojoComponentMessageController: RequestHandler = async (req, res) => {
 | 
					export const setDojoComponentMessageController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const guild = await getGuildForRequest(req);
 | 
					    const guild = await getGuildForRequest(req);
 | 
				
			||||||
    // At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the message.
 | 
					    // At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the message.
 | 
				
			||||||
    const component = guild.DojoComponents!.find(x => x._id.equals(req.query.componentId as string))!;
 | 
					    const component = guild.DojoComponents.id(req.query.componentId as string)!;
 | 
				
			||||||
    const payload = JSON.parse(String(req.body)) as SetDojoComponentMessageRequest;
 | 
					    const payload = JSON.parse(String(req.body)) as SetDojoComponentMessageRequest;
 | 
				
			||||||
    if ("Name" in payload) {
 | 
					    if ("Name" in payload) {
 | 
				
			||||||
        component.Name = payload.Name;
 | 
					        component.Name = payload.Name;
 | 
				
			||||||
@ -12,7 +12,7 @@ export const setDojoComponentMessageController: RequestHandler = async (req, res
 | 
				
			|||||||
        component.Message = payload.Message;
 | 
					        component.Message = payload.Message;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
    res.json(getDojoClient(guild, 1));
 | 
					    res.json(await getDojoClient(guild, 0, component._id));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SetDojoComponentMessageRequest = { Name: string } | { Message: string };
 | 
					type SetDojoComponentMessageRequest = { Name: string } | { Message: string };
 | 
				
			||||||
 | 
				
			|||||||
@ -1,14 +1,21 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setEquippedInstrumentController: RequestHandler = async (req, res) => {
 | 
					export const setEquippedInstrumentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					 | 
				
			||||||
    const body = getJSONfromString<ISetEquippedInstrumentRequest>(String(req.body));
 | 
					    const body = getJSONfromString<ISetEquippedInstrumentRequest>(String(req.body));
 | 
				
			||||||
    inventory.EquippedInstrument = body.Instrument;
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await Inventory.updateOne(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            accountOwnerId: accountId
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            EquippedInstrument: body.Instrument
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										59
									
								
								src/controllers/api/setGuildMotdController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/controllers/api/setGuildMotdController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { hasGuildPermissionEx } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setGuildMotdController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(account._id.toString(), "GuildId");
 | 
				
			||||||
 | 
					    const guild = (await Guild.findById(inventory.GuildId!))!;
 | 
				
			||||||
 | 
					    const member = (await GuildMember.findOne({ accountId: account._id, guildId: guild._id }))!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const IsLongMOTD = "longMOTD" in req.query;
 | 
				
			||||||
 | 
					    const MOTD = req.body ? String(req.body) : undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ("alliance" in req.query) {
 | 
				
			||||||
 | 
					        if (member.rank > 1) {
 | 
				
			||||||
 | 
					            res.status(400).json("Invalid permission");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const alliance = (await Alliance.findById(guild.AllianceId!))!;
 | 
				
			||||||
 | 
					        const motd = MOTD
 | 
				
			||||||
 | 
					            ? ({
 | 
				
			||||||
 | 
					                  message: MOTD,
 | 
				
			||||||
 | 
					                  authorName: getSuffixedName(account),
 | 
				
			||||||
 | 
					                  authorGuildName: guild.Name
 | 
				
			||||||
 | 
					              } satisfies ILongMOTD)
 | 
				
			||||||
 | 
					            : undefined;
 | 
				
			||||||
 | 
					        if (IsLongMOTD) {
 | 
				
			||||||
 | 
					            alliance.LongMOTD = motd;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            alliance.MOTD = motd;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await alliance.save();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        if (!hasGuildPermissionEx(guild, member, GuildPermission.Herald)) {
 | 
				
			||||||
 | 
					            res.status(400).json("Invalid permission");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (IsLongMOTD) {
 | 
				
			||||||
 | 
					            if (MOTD) {
 | 
				
			||||||
 | 
					                guild.LongMOTD = {
 | 
				
			||||||
 | 
					                    message: MOTD,
 | 
				
			||||||
 | 
					                    authorName: getSuffixedName(account)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                guild.LongMOTD = undefined;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            guild.MOTD = MOTD ?? "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json({ IsLongMOTD, MOTD });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,11 +1,18 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setSupportedSyndicateController: RequestHandler = async (req, res) => {
 | 
					export const setSupportedSyndicateController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					
 | 
				
			||||||
    inventory.SupportedSyndicate = req.query.syndicate as string;
 | 
					    await Inventory.updateOne(
 | 
				
			||||||
    await inventory.save();
 | 
					        {
 | 
				
			||||||
 | 
					            accountOwnerId: accountId
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            SupportedSyndicate: req.query.syndicate as string
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,20 +1,25 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { WeaponTypeInternal } from "@/src/services/itemDataService";
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setWeaponSkillTreeController: RequestHandler = async (req, res) => {
 | 
					export const setWeaponSkillTreeController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					 | 
				
			||||||
    const payload = getJSONfromString<ISetWeaponSkillTreeRequest>(String(req.body));
 | 
					    const payload = getJSONfromString<ISetWeaponSkillTreeRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const item = inventory[req.query.Category as WeaponTypeInternal].find(
 | 
					    if (equipmentKeys.indexOf(req.query.Category as TEquipmentKey) != -1) {
 | 
				
			||||||
        item => item._id.toString() == (req.query.ItemId as string)
 | 
					        await Inventory.updateOne(
 | 
				
			||||||
    )!;
 | 
					            {
 | 
				
			||||||
    item.SkillTree = payload.SkillTree;
 | 
					                accountOwnerId: accountId,
 | 
				
			||||||
 | 
					                [`${req.query.Category as string}._id`]: req.query.ItemId as string
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                [`${req.query.Category as string}.$.SkillTree`]: payload.SkillTree
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user