forked from OpenWF/SpaceNinjaServer
		
	Compare commits
	
		
			247 Commits
		
	
	
		
			3a904753f2
			...
			8b0ba0b84a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8b0ba0b84a | |||
| cdead6fdf8 | |||
| da6067ec43 | |||
| a98e18d511 | |||
| 6394adb0f0 | |||
| bc5dc02fc9 | |||
| de5fd5fce0 | |||
| a6d4fab595 | |||
| f549b042d6 | |||
| 0c34c87d75 | |||
| 3baf6ad015 | |||
| 196182f9a8 | |||
| 379f57be2c | |||
| 0d8f5ee66c | |||
| 79492efbb4 | |||
| decbbdc81b | |||
| 41d976d362 | |||
| f94ecbfbfc | |||
| f4f1e11b31 | |||
| e38d52fb1b | |||
| 419096f603 | |||
| 76a53bb1f6 | |||
| 435aafeaae | |||
| 8a1603a661 | |||
| 66e34b7be9 | |||
| 9940024a01 | |||
| ed217bae33 | |||
| 16e850e7ee | |||
| 850a073594 | |||
| 66dae6d3f8 | |||
| 9a50c05205 | |||
| 379e83a764 | |||
| 44da0eb50a | |||
| deb652ab37 | |||
| 0ea67ea89a | |||
| 51b82df5fd | |||
| 3d1b009bdb | |||
| 46aef2c00e | |||
| 7d607b7348 | |||
| 4cb1ea94e5 | |||
| 729061951f | |||
| 38502f10bf | |||
| a738dbfa9a | |||
| 95562a97ad | |||
| c13615c4df | |||
| eb6b1c1f57 | |||
| 64fbdf6064 | |||
| 47551e93b3 | |||
| 7a53363b1b | |||
| ea0ca8c88b | |||
| a10c3b061a | |||
| 3165d9f459 | |||
| 28d7ca8ca0 | |||
| 3f0a2bec48 | |||
| d28437b658 | |||
| a6d2c8b18a | |||
| 0c884576bd | |||
| 380f0662a4 | |||
| bd83738168 | |||
| fa68a1357d | |||
| 43f3917b09 | |||
| 8ebb749732 | |||
| 827ea47468 | |||
| c64d466ce1 | |||
| c8ae3d688f | |||
| 4a971841a1 | |||
| 9472f855b6 | |||
| 7736a2bf65 | |||
| bef3aeed72 | |||
| aacd089123 | |||
| d281e929ae | |||
| 729ea0abff | |||
| a75e0c59af | |||
| b429eed46c | |||
| 92d2616dda | |||
| 37ccd33d5c | |||
| 20326fdaa0 | |||
| 6a97a0c7c8 | |||
| 0928b842ad | |||
| e0200b2111 | |||
| 18a13911ba | |||
| 2c53d17489 | |||
| 2eb28c4e89 | |||
| 2187d9cd7e | |||
| e5e6f7963b | |||
| 900c6e9a26 | |||
| f0ee1e8aad | |||
| 5c6b4b5779 | |||
| 9b330ffd3e | |||
| 97d27e8110 | |||
| 525e3067c9 | |||
| 0c1fa05e9c | |||
| 946f3129b8 | |||
| 355de3fa04 | |||
| 61e168e444 | |||
| 70fa48ab07 | |||
| dde95c2b61 | |||
| 2ca79ef898 | |||
| 63e3c96671 | |||
| f0351489be | |||
| 5149d0e382 | |||
| ec8982a921 | |||
| cc338c2173 | |||
| 85b5bb438e | |||
| 9f727789ca | |||
| b308b91f44 | |||
| fc3ef3a126 | |||
| 3f47f89b56 | |||
| e784b2dfb8 | |||
| c0947b8822 | |||
| a0b61bec12 | |||
| d3620c00e2 | |||
| 0ffcee5faf | |||
| c2ed8b40f0 | |||
| feb1dd4715 | |||
| 540961ff9e | |||
| 5692a6201e | |||
| 541b8d32a8 | |||
| 74f9d1567f | |||
| 02a4d2b30a | |||
| a8f174bce1 | |||
| db1dd21924 | |||
| 005350bde0 | |||
| 5cd18db7a7 | |||
| bb315eaafe | |||
| 327b834b07 | |||
| 39be095818 | |||
| ef4973e694 | |||
| 7f69667171 | |||
| dcdeb0cd34 | |||
| 8ce86ad4fd | |||
| a2f1469779 | |||
| dd32e082f3 | |||
| 74c7d86090 | |||
| 7fd4d50e07 | |||
| 7f805a1dcc | |||
| 919f12b8f9 | |||
| 94993a16aa | |||
| 002b0cb93f | |||
| 4362a842ff | |||
| 5702ab5f3b | |||
| fac52bfda1 | |||
| 2ff535e7ab | |||
| 9698baa979 | |||
| ceb7deec06 | |||
| fe0b745066 | |||
| 8f41d3c13f | |||
| f906cdb5e8 | |||
| ea6facf3fc | |||
| b93a4a6dae | |||
| 64da8c2e50 | |||
| b2497ded19 | |||
| 5f6b2330af | |||
| 2bdb722986 | |||
| 3c79f910a2 | |||
| 65306e0478 | |||
| 49edebc1eb | |||
| 62263efde3 | |||
| f66c958a3c | |||
| 2ef59cd570 | |||
| 6bb74b026a | |||
| 743a905de4 | |||
| 5c22949c6b | |||
| 651640c4d7 | |||
| 1d1abf5550 | |||
| 23267aa641 | |||
| d94b4fd946 | |||
| d5ff349746 | |||
| 2746e243c9 | |||
| b3374eb66e | |||
| c18abab9c4 | |||
| 61062e433f | |||
| 92e8ffd709 | |||
| 0b18932dd8 | |||
| abeb17ce44 | |||
| 710470ca2d | |||
| 5cc991baca | |||
| 9eadc7fa21 | |||
| ed10a89c1d | |||
| 0c2f72f9b1 | |||
| d918b0c982 | |||
| 05c0c9909c | |||
| 92cf85084f | |||
| cfa9ec775e | |||
| d4d887a5a4 | |||
| 1b7b5a28bc | |||
| 6dc54ed893 | |||
| 2173bdb8b8 | |||
| 74d9428a66 | |||
| dd7805cfb2 | |||
| c55aa8a0e1 | |||
| 24ed580a97 | |||
| 158310bda2 | |||
| 2b451a19e6 | |||
| bf67a4391d | |||
| ea9333279b | |||
| 9e1a5d50af | |||
| 2091dabfc3 | |||
| 3a26d788a2 | |||
| 367dd3f22d | |||
| 404c747642 | |||
| 1a4ad8b7a5 | |||
| 4a3a3de300 | |||
| 7d5ea680e4 | |||
| e2879a7808 | |||
| d033c2bc12 | |||
| fb58aeb07f | |||
| 3d69828610 | |||
| a0fa41cd58 | |||
| 9162522962 | |||
| 42e08faaaf | |||
| 9e0dd3e0a5 | |||
| 054abee62c | |||
| 04d39ed973 | |||
| b0f0b61d49 | |||
| 23f8901505 | |||
| d3d966a503 | |||
| 48598c145f | |||
| 01f04c287a | |||
| 9e99d0370c | |||
| 516f822e43 | |||
| fccdbf4a8e | |||
| cfc1524619 | |||
| 3beb1ecc42 | |||
| 779bc34082 | |||
| b6167165fe | |||
| b07e89ed72 | |||
| f34e1615e2 | |||
| c82cad7b02 | |||
| 725efcc72e | |||
| fa34b99976 | |||
| d3819c25c5 | |||
| 4cb883dabf | |||
| c376ff25f3 | |||
| f7ada5a7e5 | |||
| 1bdc5126b3 | |||
| a7899d1c18 | |||
| 895b9381ca | |||
| 9de0aee6f0 | |||
| 69f544c8d1 | |||
| 8cdcb209ae | |||
| ab0d472c75 | |||
| e266f9e36c | |||
| 6a1e508109 | |||
| a167216730 | |||
| 30ae95bec8 | |||
| dcc2b903ac | 
@ -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
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@ -1,4 +1,4 @@
 | 
				
			|||||||
# Auto detect text files and perform LF normalization
 | 
					# Auto detect text files and perform LF normalization
 | 
				
			||||||
* text=auto
 | 
					* text=auto eol=lf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static/webui/libs/ linguist-vendored
 | 
					static/webui/libs/ linguist-vendored
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@ -5,17 +5,22 @@ on:
 | 
				
			|||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
    build:
 | 
					    build:
 | 
				
			||||||
        runs-on: ubuntu-latest
 | 
					        runs-on: ubuntu-latest
 | 
				
			||||||
        strategy:
 | 
					 | 
				
			||||||
            matrix:
 | 
					 | 
				
			||||||
                version: [18, 20, 22]
 | 
					 | 
				
			||||||
        steps:
 | 
					        steps:
 | 
				
			||||||
            - name: Checkout
 | 
					            - name: Checkout
 | 
				
			||||||
              uses: actions/checkout@v4.1.2
 | 
					              uses: actions/checkout@v4.1.2
 | 
				
			||||||
            - name: Setup Node.js environment
 | 
					            - name: Setup Node.js environment
 | 
				
			||||||
              uses: actions/setup-node@v4.0.2
 | 
					              uses: actions/setup-node@v4.0.2
 | 
				
			||||||
              with:
 | 
					 | 
				
			||||||
                  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:ci
 | 
				
			||||||
 | 
					            - run: npm run prettier
 | 
				
			||||||
 | 
					            - run: npm run update-translations
 | 
				
			||||||
 | 
					            - name: Fail if there are uncommitted changes
 | 
				
			||||||
 | 
					              run: |
 | 
				
			||||||
 | 
					                  if [[ -n "$(git status --porcelain)" ]]; then
 | 
				
			||||||
 | 
					                    echo "Uncommitted changes detected:"
 | 
				
			||||||
 | 
					                    git status
 | 
				
			||||||
 | 
					                    git --no-pager diff
 | 
				
			||||||
 | 
					                    exit 1
 | 
				
			||||||
 | 
					                  fi
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					src/routes/api.ts
 | 
				
			||||||
static/webui/libs/
 | 
					static/webui/libs/
 | 
				
			||||||
*.html
 | 
					*.html
 | 
				
			||||||
*.md
 | 
					*.md
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "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
 | 
				
			||||||
 | 
				
			|||||||
@ -10,5 +10,8 @@ To get an idea of what functionality you can expect to be missing [have a look t
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## config.json
 | 
					## config.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), which has most cheats disabled.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `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.
 | 
				
			||||||
 | 
				
			|||||||
@ -29,19 +29,26 @@
 | 
				
			|||||||
  "unlockExilusEverywhere": false,
 | 
					  "unlockExilusEverywhere": false,
 | 
				
			||||||
  "unlockArcanesEverywhere": false,
 | 
					  "unlockArcanesEverywhere": false,
 | 
				
			||||||
  "noDailyStandingLimits": false,
 | 
					  "noDailyStandingLimits": false,
 | 
				
			||||||
 | 
					  "noDailyFocusLimit": false,
 | 
				
			||||||
  "noArgonCrystalDecay": false,
 | 
					  "noArgonCrystalDecay": false,
 | 
				
			||||||
 | 
					  "noMasteryRankUpCooldown": false,
 | 
				
			||||||
  "noVendorPurchaseLimits": true,
 | 
					  "noVendorPurchaseLimits": true,
 | 
				
			||||||
 | 
					  "noDeathMarks": false,
 | 
				
			||||||
 | 
					  "noKimCooldowns": false,
 | 
				
			||||||
  "instantResourceExtractorDrones": false,
 | 
					  "instantResourceExtractorDrones": false,
 | 
				
			||||||
 | 
					  "noResourceExtractorDronesDamage": false,
 | 
				
			||||||
  "noDojoRoomBuildStage": false,
 | 
					  "noDojoRoomBuildStage": false,
 | 
				
			||||||
 | 
					  "noDecoBuildStage": false,
 | 
				
			||||||
  "fastDojoRoomDestruction": false,
 | 
					  "fastDojoRoomDestruction": false,
 | 
				
			||||||
  "noDojoResearchCosts": false,
 | 
					  "noDojoResearchCosts": false,
 | 
				
			||||||
  "noDojoResearchTime": false,
 | 
					  "noDojoResearchTime": false,
 | 
				
			||||||
  "fastClanAscension": false,
 | 
					  "fastClanAscension": false,
 | 
				
			||||||
  "spoofMasteryRank": -1,
 | 
					  "spoofMasteryRank": -1,
 | 
				
			||||||
  "events": {
 | 
					  "worldState": {
 | 
				
			||||||
    "creditBoost": false,
 | 
					    "creditBoost": false,
 | 
				
			||||||
    "affinityBoost": false,
 | 
					    "affinityBoost": false,
 | 
				
			||||||
    "resourceBoost": false,
 | 
					    "resourceBoost": false,
 | 
				
			||||||
    "starDays": true
 | 
					    "starDays": true,
 | 
				
			||||||
 | 
					    "lockTime": 0
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										638
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										638
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										23
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								package.json
									
									
									
									
									
								
							@ -4,10 +4,12 @@
 | 
				
			|||||||
  "description": "WF Emulator",
 | 
					  "description": "WF Emulator",
 | 
				
			||||||
  "main": "index.ts",
 | 
					  "main": "index.ts",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "start": "node --import ./build/src/pathman.js build/src/index.js",
 | 
					    "start": "node --enable-source-maps --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:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
 | 
				
			||||||
    "lint:fix": "eslint --fix --ext .ts .",
 | 
					    "lint:fix": "eslint --fix --ext .ts .",
 | 
				
			||||||
    "prettier": "prettier --write .",
 | 
					    "prettier": "prettier --write .",
 | 
				
			||||||
    "update-translations": "cd scripts && node update-translations.js"
 | 
					    "update-translations": "cd scripts && node update-translations.js"
 | 
				
			||||||
@ -16,24 +18,25 @@
 | 
				
			|||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@types/express": "^5",
 | 
					    "@types/express": "^5",
 | 
				
			||||||
    "@types/morgan": "^1.9.9",
 | 
					    "@types/morgan": "^1.9.9",
 | 
				
			||||||
    "copyfiles": "^2.4.1",
 | 
					 | 
				
			||||||
    "crc-32": "^1.2.2",
 | 
					    "crc-32": "^1.2.2",
 | 
				
			||||||
    "express": "^5",
 | 
					    "express": "^5",
 | 
				
			||||||
    "json-with-bigint": "^3.2.2",
 | 
					    "json-with-bigint": "^3.2.2",
 | 
				
			||||||
    "mongoose": "^8.11.0",
 | 
					    "mongoose": "^8.11.0",
 | 
				
			||||||
    "morgan": "^1.10.0",
 | 
					    "morgan": "^1.10.0",
 | 
				
			||||||
    "typescript": ">=5.5 <5.6.0",
 | 
					    "ncp": "^2.0.0",
 | 
				
			||||||
    "warframe-public-export-plus": "^0.5.48",
 | 
					    "typescript": "^5.5",
 | 
				
			||||||
 | 
					    "warframe-public-export-plus": "^0.5.56",
 | 
				
			||||||
    "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": {
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^7.18",
 | 
					    "@rxliuli/tsgo": "^2025.3.31",
 | 
				
			||||||
    "@typescript-eslint/parser": "^7.18",
 | 
					    "@typescript-eslint/eslint-plugin": "^8.28.0",
 | 
				
			||||||
    "eslint": "^8.56.0",
 | 
					    "@typescript-eslint/parser": "^8.28.0",
 | 
				
			||||||
    "eslint-plugin-prettier": "^5.2.3",
 | 
					    "eslint": "^8",
 | 
				
			||||||
    "prettier": "^3.4.2",
 | 
					    "eslint-plugin-prettier": "^5.2.5",
 | 
				
			||||||
 | 
					    "prettier": "^3.5.3",
 | 
				
			||||||
    "ts-node-dev": "^2.0.0",
 | 
					    "ts-node-dev": "^2.0.0",
 | 
				
			||||||
    "tsconfig-paths": "^4.2.0"
 | 
					    "tsconfig-paths": "^4.2.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
const fs = require("fs");
 | 
					const fs = require("fs");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function extractStrings(content) {
 | 
					function extractStrings(content) {
 | 
				
			||||||
    const regex = /([a-zA-Z_]+): `([^`]*)`,/g;
 | 
					    const regex = /([a-zA-Z0-9_]+): `([^`]*)`,/g;
 | 
				
			||||||
    let matches;
 | 
					    let matches;
 | 
				
			||||||
    const strings = {};
 | 
					    const strings = {};
 | 
				
			||||||
    while ((matches = regex.exec(content)) !== null) {
 | 
					    while ((matches = regex.exec(content)) !== null) {
 | 
				
			||||||
@ -15,7 +15,7 @@ function extractStrings(content) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
 | 
					const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
 | 
				
			||||||
const sourceStrings = extractStrings(source);
 | 
					const sourceStrings = extractStrings(source);
 | 
				
			||||||
const sourceLines = source.split("\n");
 | 
					const sourceLines = source.substring(0, source.length - 1).split("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fs.readdirSync("../static/webui/translations").forEach(file => {
 | 
					fs.readdirSync("../static/webui/translations").forEach(file => {
 | 
				
			||||||
    if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
 | 
					    if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
 | 
				
			||||||
@ -36,7 +36,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
 | 
				
			|||||||
                        fs.writeSync(fileHandle, `    ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
 | 
					                        fs.writeSync(fileHandle, `    ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            } else if (line.length) {
 | 
					            } else {
 | 
				
			||||||
                fs.writeSync(fileHandle, line + "\n");
 | 
					                fs.writeSync(fileHandle, line + "\n");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
				
			|||||||
@ -16,9 +16,9 @@ import { webuiRouter } from "@/src/routes/webui";
 | 
				
			|||||||
const app = express();
 | 
					const app = express();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.use((req, _res, next) => {
 | 
					app.use((req, _res, next) => {
 | 
				
			||||||
    // 38.5.0 introduced "ezip" for encrypted body blobs.
 | 
					    // 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
 | 
				
			||||||
    // The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
 | 
					    // 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") {
 | 
					    if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
 | 
				
			||||||
        req.headers["content-encoding"] = undefined;
 | 
					        req.headers["content-encoding"] = undefined;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    next();
 | 
					    next();
 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
 | 
				
			|||||||
    if (request.DecoId) {
 | 
					    if (request.DecoId) {
 | 
				
			||||||
        removeDojoDeco(guild, request.ComponentId, request.DecoId);
 | 
					        removeDojoDeco(guild, request.ComponentId, request.DecoId);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        removeDojoRoom(guild, request.ComponentId);
 | 
					        await removeDojoRoom(guild, request.ComponentId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										30
									
								
								src/controllers/api/addIgnoredUserController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/controllers/api/addIgnoredUserController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Account, Ignore } from "@/src/models/loginModel";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IFriendInfo } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addIgnoredUserController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const data = getJSONfromString<IAddIgnoredUserRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const ignoreeAccount = await Account.findOne(
 | 
				
			||||||
 | 
					        { DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
 | 
				
			||||||
 | 
					        "_id"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (ignoreeAccount) {
 | 
				
			||||||
 | 
					        await Ignore.create({ ignorer: accountId, ignoree: ignoreeAccount._id });
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            Ignored: {
 | 
				
			||||||
 | 
					                _id: toOid(ignoreeAccount._id),
 | 
				
			||||||
 | 
					                DisplayName: data.playerName
 | 
				
			||||||
 | 
					            } satisfies IFriendInfo
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        res.status(400).end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IAddIgnoredUserRequest {
 | 
				
			||||||
 | 
					    playerName: 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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -3,15 +3,19 @@ import { Account } from "@/src/models/loginModel";
 | 
				
			|||||||
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
 | 
					import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
 | 
				
			||||||
import { createMessage } from "@/src/services/inboxService";
 | 
					import { createMessage } from "@/src/services/inboxService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
					import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
import { IOid } from "@/src/types/commonTypes";
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
 | 
					import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportFlavour } from "warframe-public-export-plus";
 | 
					import { ExportFlavour } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addToGuildController: RequestHandler = async (req, res) => {
 | 
					export const addToGuildController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
 | 
					    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 });
 | 
					        const account = await Account.findOne({ DisplayName: payload.UserName });
 | 
				
			||||||
        if (!account) {
 | 
					        if (!account) {
 | 
				
			||||||
            res.status(400).json("Username does not exist");
 | 
					            res.status(400).json("Username does not exist");
 | 
				
			||||||
@ -25,30 +29,26 @@ export const addToGuildController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const guild = (await Guild.findById(payload.GuildId.$oid, "Name"))!;
 | 
					        const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
 | 
				
			||||||
        const senderAccount = await getAccountForRequest(req);
 | 
					        const senderAccount = await getAccountForRequest(req);
 | 
				
			||||||
        if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
 | 
					        if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
 | 
				
			||||||
            res.status(400).json("Invalid permission");
 | 
					            res.status(400).json("Invalid permission");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (
 | 
					        try {
 | 
				
			||||||
        await GuildMember.exists({
 | 
					 | 
				
			||||||
            accountId: account._id,
 | 
					 | 
				
			||||||
            guildId: payload.GuildId.$oid
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        res.status(400).json("User already invited to clan");
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            await GuildMember.insertOne({
 | 
					            await GuildMember.insertOne({
 | 
				
			||||||
                accountId: account._id,
 | 
					                accountId: account._id,
 | 
				
			||||||
                guildId: payload.GuildId.$oid,
 | 
					                guildId: payload.GuildId.$oid,
 | 
				
			||||||
                status: 2 // outgoing invite
 | 
					                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");
 | 
					        const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
 | 
				
			||||||
    await createMessage(account._id.toString(), [
 | 
					        await createMessage(account._id, [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                sndr: getSuffixedName(senderAccount),
 | 
					                sndr: getSuffixedName(senderAccount),
 | 
				
			||||||
                msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
 | 
					                msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
 | 
				
			||||||
@ -76,9 +76,30 @@ export const addToGuildController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
        await fillInInventoryDataForGuildMember(member);
 | 
					        await fillInInventoryDataForGuildMember(member);
 | 
				
			||||||
        res.json({ NewMember: 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 {
 | 
					interface IAddToGuildRequest {
 | 
				
			||||||
    UserName: string;
 | 
					    UserName?: string;
 | 
				
			||||||
    GuildId: IOid;
 | 
					    GuildId: IOid;
 | 
				
			||||||
 | 
					    RequestMsg?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -57,12 +57,16 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
 | 
				
			|||||||
        payload.Consumed.forEach(upgrade => {
 | 
					        payload.Consumed.forEach(upgrade => {
 | 
				
			||||||
            const meta = ExportUpgrades[upgrade.ItemType];
 | 
					            const meta = ExportUpgrades[upgrade.ItemType];
 | 
				
			||||||
            counts[meta.rarity] += upgrade.ItemCount;
 | 
					            counts[meta.rarity] += upgrade.ItemCount;
 | 
				
			||||||
 | 
					            if (upgrade.ItemId.$oid != "000000000000000000000000") {
 | 
				
			||||||
 | 
					                inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
                addMods(inventory, [
 | 
					                addMods(inventory, [
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        ItemType: upgrade.ItemType,
 | 
					                        ItemType: upgrade.ItemType,
 | 
				
			||||||
                        ItemCount: upgrade.ItemCount * -1
 | 
					                        ItemCount: upgrade.ItemCount * -1
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                ]);
 | 
					                ]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
 | 
					            if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
 | 
				
			||||||
                forcedPolarity = "AP_ATTACK";
 | 
					                forcedPolarity = "AP_ATTACK";
 | 
				
			||||||
            } else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
 | 
					            } else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
 | 
				
			||||||
@ -72,6 +76,15 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let newModType: string | undefined;
 | 
				
			||||||
 | 
					        for (const specialModSet of specialModSets) {
 | 
				
			||||||
 | 
					            if (specialModSet.indexOf(payload.Consumed[0].ItemType) != -1) {
 | 
				
			||||||
 | 
					                newModType = getRandomElement(specialModSet);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!newModType) {
 | 
				
			||||||
            // Based on the table on https://wiki.warframe.com/w/Transmutation
 | 
					            // Based on the table on https://wiki.warframe.com/w/Transmutation
 | 
				
			||||||
            const weights: Record<TRarity, number> = {
 | 
					            const weights: Record<TRarity, number> = {
 | 
				
			||||||
                COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
 | 
					                COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
 | 
				
			||||||
@ -87,7 +100,9 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const newModType = getRandomWeightedReward(options, weights)!.uniqueName;
 | 
					            newModType = getRandomWeightedReward(options, weights)!.uniqueName;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        addMods(inventory, [
 | 
					        addMods(inventory, [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                ItemType: newModType,
 | 
					                ItemType: newModType,
 | 
				
			||||||
@ -130,3 +145,34 @@ interface IAgnosticUpgradeClient {
 | 
				
			|||||||
    ItemCount: number;
 | 
					    ItemCount: number;
 | 
				
			||||||
    LastAdded: IOid;
 | 
					    LastAdded: IOid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const specialModSets: string[][] = [
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/ImmortalWildcardMod"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    [
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod",
 | 
				
			||||||
 | 
					        "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -15,6 +15,12 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Example POST body: {"pivot":[0, 0, -64],"components":"{\"670429301ca0a63848ccc467\":{\"R\":[0,0,0],\"P\":[0,3,32]},\"6704254a1ca0a63848ccb33c\":{\"R\":[0,0,0],\"P\":[0,9.25,-32]},\"670429461ca0a63848ccc731\":{\"R\":[-90,0,0],\"P\":[-47.999992370605,3,16]}}"}
 | 
				
			||||||
 | 
					    if (req.body) {
 | 
				
			||||||
 | 
					        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
 | 
					        throw new Error("dojo reparent operation should not need deco repositioning"); // because we always provide SortId
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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()] = {
 | 
				
			||||||
@ -43,23 +49,13 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    newRoot.component.pp = undefined;
 | 
					    newRoot.component.pp = undefined;
 | 
				
			||||||
    newRoot.parent = undefined;
 | 
					    newRoot.parent = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Don't even ask me why this is needed because I don't know either
 | 
					    // Set/update SortId in top-to-bottom order
 | 
				
			||||||
    const stack: INode[] = [newRoot];
 | 
					    const stack: INode[] = [newRoot];
 | 
				
			||||||
    let i = 0;
 | 
					 | 
				
			||||||
    const idMap: Record<string, Types.ObjectId> = {};
 | 
					 | 
				
			||||||
    while (stack.length != 0) {
 | 
					    while (stack.length != 0) {
 | 
				
			||||||
        const top = stack.shift()!;
 | 
					        const top = stack.shift()!;
 | 
				
			||||||
        idMap[top.component._id.toString()] = new Types.ObjectId(
 | 
					        top.component.SortId = new Types.ObjectId();
 | 
				
			||||||
            (++i).toString(16).padStart(8, "0") + top.component._id.toString().substr(8)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        top.children.forEach(x => stack.push(x));
 | 
					        top.children.forEach(x => stack.push(x));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    guild.DojoComponents.forEach(x => {
 | 
					 | 
				
			||||||
        x._id = idMap[x._id.toString()];
 | 
					 | 
				
			||||||
        if (x.pi) {
 | 
					 | 
				
			||||||
            x.pi = idMap[x.pi.toString()];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.debug("New tree:\n" + treeToString(newRoot));
 | 
					    logger.debug("New tree:\n" + treeToString(newRoot));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -18,8 +18,9 @@ import {
 | 
				
			|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IClaimCompletedRecipeRequest {
 | 
					interface IClaimCompletedRecipeRequest {
 | 
				
			||||||
    RecipeIds: IOid[];
 | 
					    RecipeIds: IOid[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -80,6 +81,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
					        logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let BrandedSuits: undefined | IOid[];
 | 
				
			||||||
        if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
					        if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
				
			||||||
            inventory.PendingSpectreLoadouts ??= [];
 | 
					            inventory.PendingSpectreLoadouts ??= [];
 | 
				
			||||||
            inventory.SpectreLoadouts ??= [];
 | 
					            inventory.SpectreLoadouts ??= [];
 | 
				
			||||||
@ -99,9 +101,15 @@ 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
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let InventoryChanges = {};
 | 
					        let InventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
        if (recipe.consumeOnUse) {
 | 
					        if (recipe.consumeOnUse) {
 | 
				
			||||||
            addRecipes(inventory, [
 | 
					            addRecipes(inventory, [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -111,16 +119,24 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
				
			|||||||
            ]);
 | 
					            ]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        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 = {
 | 
				
			||||||
                ...InventoryChanges,
 | 
					                ...InventoryChanges,
 | 
				
			||||||
                ...(await addItem(inventory, recipe.resultType, recipe.num, false))
 | 
					                ...(await addItem(inventory, recipe.resultType, recipe.num, false))
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        res.json({ InventoryChanges });
 | 
					        res.json({ InventoryChanges, BrandedSuits });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,8 @@ export const clearDialogueHistoryController: RequestHandler = async (req, res) =
 | 
				
			|||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
 | 
					    const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
 | 
				
			||||||
    if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
 | 
					    if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
 | 
				
			||||||
 | 
					        inventory.DialogueHistory.Resets ??= 0;
 | 
				
			||||||
 | 
					        inventory.DialogueHistory.Resets += 1;
 | 
				
			||||||
        for (const dialogueName of request.Dialogues) {
 | 
					        for (const dialogueName of request.Dialogues) {
 | 
				
			||||||
            const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
 | 
					            const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
 | 
				
			||||||
            if (index != -1) {
 | 
					            if (index != -1) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,26 +1,59 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
 | 
					import { Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
import { getGuildClient, updateInventoryForConfirmedGuildJoin } from "@/src/services/guildService";
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
					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 { RequestHandler } from "express";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const confirmGuildInvitationController: RequestHandler = async (req, res) => {
 | 
					// 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 account = await getAccountForRequest(req);
 | 
				
			||||||
    const guildMember = await GuildMember.findOne({
 | 
					    const invitedGuildMember = await GuildMember.findOne({
 | 
				
			||||||
        accountId: account._id,
 | 
					        accountId: account._id,
 | 
				
			||||||
        guildId: req.query.clanId as string
 | 
					        guildId: req.query.clanId as string
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    if (guildMember) {
 | 
					    if (invitedGuildMember && invitedGuildMember.status == 2) {
 | 
				
			||||||
        guildMember.status = 0;
 | 
					        let inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
        await guildMember.save();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await updateInventoryForConfirmedGuildJoin(
 | 
					        // If this account is already in a guild, we need to do cleanup first.
 | 
				
			||||||
            account._id.toString(),
 | 
					        const guildMember = await GuildMember.findOneAndDelete({ accountId: account._id, status: 0 });
 | 
				
			||||||
            new Types.ObjectId(req.query.clanId as string)
 | 
					        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))!;
 | 
					        const guild = (await Guild.findById(req.query.clanId as string))!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Add join to clan log
 | 
				
			||||||
        guild.RosterActivity ??= [];
 | 
					        guild.RosterActivity ??= [];
 | 
				
			||||||
        guild.RosterActivity.push({
 | 
					        guild.RosterActivity.push({
 | 
				
			||||||
            dateTime: new Date(),
 | 
					            dateTime: new Date(),
 | 
				
			||||||
@ -31,16 +64,61 @@ export const confirmGuildInvitationController: RequestHandler = async (req, res)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        res.json({
 | 
					        res.json({
 | 
				
			||||||
            ...(await getGuildClient(guild, account._id.toString())),
 | 
					            ...(await getGuildClient(guild, account._id.toString())),
 | 
				
			||||||
            InventoryChanges: {
 | 
					            InventoryChanges: inventoryChanges
 | 
				
			||||||
                Recipes: [
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
 | 
					 | 
				
			||||||
                        ItemCount: 1
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                ]
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        res.end();
 | 
					        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
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,7 @@
 | 
				
			|||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
					import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
 | 
					import { Guild } from "@/src/models/guildModel";
 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService";
 | 
				
			||||||
import { createMessage } from "@/src/services/inboxService";
 | 
					 | 
				
			||||||
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 { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
@ -31,43 +30,7 @@ export const contributeGuildClassController: RequestHandler = async (req, res) =
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    guild.CeremonyContributors.push(new Types.ObjectId(accountId));
 | 
					    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.
 | 
					    await checkClanAscensionHasRequiredContributors(guild);
 | 
				
			||||||
    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.toString(), [
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        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();
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
 | 
					import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
 | 
				
			||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    addGuildMemberMiscItemContribution,
 | 
				
			||||||
    getDojoClient,
 | 
					    getDojoClient,
 | 
				
			||||||
    getGuildForRequestEx,
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
    hasAccessToDojo,
 | 
					    hasAccessToDojo,
 | 
				
			||||||
@ -63,9 +64,7 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					    await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
 | 
				
			||||||
    await inventory.save();
 | 
					 | 
				
			||||||
    await guildMember.save();
 | 
					 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        ...(await getDojoClient(guild, 0, component._id)),
 | 
					        ...(await getDojoClient(guild, 0, component._id)),
 | 
				
			||||||
        InventoryChanges: inventoryChanges
 | 
					        InventoryChanges: inventoryChanges
 | 
				
			||||||
@ -94,10 +93,10 @@ const processContribution = (
 | 
				
			|||||||
        component.RegularCredits += request.VaultCredits;
 | 
					        component.RegularCredits += request.VaultCredits;
 | 
				
			||||||
        guild.VaultRegularCredits! -= request.VaultCredits;
 | 
					        guild.VaultRegularCredits! -= request.VaultCredits;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (component.RegularCredits > scaleRequiredCount(meta.price)) {
 | 
					    if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
 | 
				
			||||||
        guild.VaultRegularCredits ??= 0;
 | 
					        guild.VaultRegularCredits ??= 0;
 | 
				
			||||||
        guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(meta.price);
 | 
					        guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
 | 
				
			||||||
        component.RegularCredits = scaleRequiredCount(meta.price);
 | 
					        component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    component.MiscItems ??= [];
 | 
					    component.MiscItems ??= [];
 | 
				
			||||||
@ -108,10 +107,10 @@ const processContribution = (
 | 
				
			|||||||
                const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
					                const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
				
			||||||
                if (
 | 
					                if (
 | 
				
			||||||
                    componentMiscItem.ItemCount + ingredientContribution.ItemCount >
 | 
					                    componentMiscItem.ItemCount + ingredientContribution.ItemCount >
 | 
				
			||||||
                    scaleRequiredCount(ingredientMeta.ItemCount)
 | 
					                    scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
                    ingredientContribution.ItemCount =
 | 
					                    ingredientContribution.ItemCount =
 | 
				
			||||||
                        scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
 | 
					                        scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                componentMiscItem.ItemCount += ingredientContribution.ItemCount;
 | 
					                componentMiscItem.ItemCount += ingredientContribution.ItemCount;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
@ -129,10 +128,10 @@ const processContribution = (
 | 
				
			|||||||
                const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
					                const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
				
			||||||
                if (
 | 
					                if (
 | 
				
			||||||
                    componentMiscItem.ItemCount + ingredientContribution.ItemCount >
 | 
					                    componentMiscItem.ItemCount + ingredientContribution.ItemCount >
 | 
				
			||||||
                    scaleRequiredCount(ingredientMeta.ItemCount)
 | 
					                    scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
                    ingredientContribution.ItemCount =
 | 
					                    ingredientContribution.ItemCount =
 | 
				
			||||||
                        scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
 | 
					                        scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                componentMiscItem.ItemCount += ingredientContribution.ItemCount;
 | 
					                componentMiscItem.ItemCount += ingredientContribution.ItemCount;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
@ -143,18 +142,20 @@ const processContribution = (
 | 
				
			|||||||
                ItemCount: ingredientContribution.ItemCount * -1
 | 
					                ItemCount: ingredientContribution.ItemCount * -1
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            guildMember.MiscItemsContributed ??= [];
 | 
					            addGuildMemberMiscItemContribution(guildMember, ingredientContribution);
 | 
				
			||||||
            guildMember.MiscItemsContributed.push(ingredientContribution);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        addMiscItems(inventory, miscItemChanges);
 | 
					        addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
        inventoryChanges.MiscItems = miscItemChanges;
 | 
					        inventoryChanges.MiscItems = miscItemChanges;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (component.RegularCredits >= scaleRequiredCount(meta.price)) {
 | 
					    if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
 | 
				
			||||||
        let fullyFunded = true;
 | 
					        let fullyFunded = true;
 | 
				
			||||||
        for (const ingredient of meta.ingredients) {
 | 
					        for (const ingredient of meta.ingredients) {
 | 
				
			||||||
            const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
 | 
					            const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
 | 
				
			||||||
            if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
 | 
					            if (
 | 
				
			||||||
 | 
					                !componentMiscItem ||
 | 
				
			||||||
 | 
					                componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
                fullyFunded = false;
 | 
					                fullyFunded = false;
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,56 +1,104 @@
 | 
				
			|||||||
import { GuildMember } from "@/src/models/guildModel";
 | 
					import {
 | 
				
			||||||
import { getGuildForRequestEx } from "@/src/services/guildService";
 | 
					    Alliance,
 | 
				
			||||||
import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
 | 
					    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 { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const contributeToVaultController: RequestHandler = async (req, res) => {
 | 
					export const contributeToVaultController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    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 guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
    const guildMember = (await GuildMember.findOne(
 | 
					        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 },
 | 
					            { accountId, guildId: guild._id },
 | 
				
			||||||
            "RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
 | 
					            "RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
 | 
				
			||||||
        ))!;
 | 
					        ))!;
 | 
				
			||||||
    const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (request.RegularCredits) {
 | 
					    if (request.RegularCredits) {
 | 
				
			||||||
 | 
					        updateCurrency(inventory, request.RegularCredits, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        guild.VaultRegularCredits ??= 0;
 | 
					        guild.VaultRegularCredits ??= 0;
 | 
				
			||||||
        guild.VaultRegularCredits += request.RegularCredits;
 | 
					        guild.VaultRegularCredits += request.RegularCredits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (guildMember) {
 | 
				
			||||||
            guildMember.RegularCreditsContributed ??= 0;
 | 
					            guildMember.RegularCreditsContributed ??= 0;
 | 
				
			||||||
            guildMember.RegularCreditsContributed += request.RegularCredits;
 | 
					            guildMember.RegularCreditsContributed += request.RegularCredits;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (request.MiscItems.length) {
 | 
					    if (request.MiscItems.length) {
 | 
				
			||||||
        guild.VaultMiscItems ??= [];
 | 
					        addVaultMiscItems(guild, request.MiscItems);
 | 
				
			||||||
        guildMember.MiscItemsContributed ??= [];
 | 
					 | 
				
			||||||
        for (const item of request.MiscItems) {
 | 
					        for (const item of request.MiscItems) {
 | 
				
			||||||
            guild.VaultMiscItems.push(item);
 | 
					            if (guildMember) {
 | 
				
			||||||
            guildMember.MiscItemsContributed.push(item);
 | 
					                addGuildMemberMiscItemContribution(guildMember, item);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
 | 
					            addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (request.ShipDecorations.length) {
 | 
					    if (request.ShipDecorations.length) {
 | 
				
			||||||
        guild.VaultShipDecorations ??= [];
 | 
					        addVaultShipDecos(guild, request.ShipDecorations);
 | 
				
			||||||
        guildMember.ShipDecorationsContributed ??= [];
 | 
					 | 
				
			||||||
        for (const item of request.ShipDecorations) {
 | 
					        for (const item of request.ShipDecorations) {
 | 
				
			||||||
            guild.VaultShipDecorations.push(item);
 | 
					            if (guildMember) {
 | 
				
			||||||
            guildMember.ShipDecorationsContributed.push(item);
 | 
					                addGuildMemberShipDecoContribution(guildMember, item);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
 | 
					            addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (request.FusionTreasures.length) {
 | 
					    if (request.FusionTreasures.length) {
 | 
				
			||||||
        guild.VaultFusionTreasures ??= [];
 | 
					        addVaultFusionTreasures(guild, request.FusionTreasures);
 | 
				
			||||||
        for (const item of request.FusionTreasures) {
 | 
					        for (const item of request.FusionTreasures) {
 | 
				
			||||||
            guild.VaultFusionTreasures.push(item);
 | 
					 | 
				
			||||||
            addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
 | 
					            addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					    const promises: Promise<unknown>[] = [guild.save(), inventory.save()];
 | 
				
			||||||
    await inventory.save();
 | 
					    if (guildMember) {
 | 
				
			||||||
    await guildMember.save();
 | 
					        promises.push(guildMember.save());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await Promise.all(promises);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -59,4 +107,7 @@ interface IContributeToVaultRequest {
 | 
				
			|||||||
    MiscItems: IMiscItem[];
 | 
					    MiscItems: IMiscItem[];
 | 
				
			||||||
    ShipDecorations: ITypeCount[];
 | 
					    ShipDecorations: ITypeCount[];
 | 
				
			||||||
    FusionTreasures: IFusionTreasure[];
 | 
					    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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,16 +2,16 @@ 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 { Guild, GuildMember } from "@/src/models/guildModel";
 | 
					import { Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
import {
 | 
					import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
 | 
				
			||||||
    createUniqueClanName,
 | 
					import { addRecipes, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
    getGuildClient,
 | 
					 | 
				
			||||||
    updateInventoryForConfirmedGuildJoin
 | 
					 | 
				
			||||||
} from "@/src/services/guildService";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
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: await createUniqueClanName(payload.guildName)
 | 
					        Name: await createUniqueClanName(payload.guildName)
 | 
				
			||||||
@ -26,9 +26,27 @@ export const createGuildController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        rank: 0
 | 
					        rank: 0
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await updateInventoryForConfirmedGuildJoin(accountId, guild._id);
 | 
					    const inventory = await getInventory(accountId, "GuildId Recipes");
 | 
				
			||||||
 | 
					    inventory.GuildId = guild._id;
 | 
				
			||||||
 | 
					    addRecipes(inventory, [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
 | 
				
			||||||
 | 
					            ItemCount: 1
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json(await getGuildClient(guild, accountId));
 | 
					    res.json({
 | 
				
			||||||
 | 
					        ...(await getGuildClient(guild, accountId)),
 | 
				
			||||||
 | 
					        InventoryChanges: {
 | 
				
			||||||
 | 
					            Recipes: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
 | 
				
			||||||
 | 
					                    ItemCount: 1
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ICreateGuildRequest {
 | 
					interface ICreateGuildRequest {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										79
									
								
								src/controllers/api/crewShipIdentifySalvageController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/controllers/api/crewShipIdentifySalvageController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    addCrewShipSalvagedWeaponSkin,
 | 
				
			||||||
 | 
					    addCrewShipRawSalvage,
 | 
				
			||||||
 | 
					    getInventory,
 | 
				
			||||||
 | 
					    addEquipment
 | 
				
			||||||
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ICrewShipComponentFingerprint, IInnateDamageFingerprint } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { ExportCustoms, ExportRailjackWeapons, ExportUpgrades } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { getRandomInt } from "@/src/services/rngService";
 | 
				
			||||||
 | 
					import { IFingerprintStat } from "@/src/helpers/rivenHelper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(
 | 
				
			||||||
 | 
					        accountId,
 | 
				
			||||||
 | 
					        "CrewShipSalvagedWeaponSkins CrewShipSalvagedWeapons CrewShipRawSalvage"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<ICrewShipIdentifySalvageRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					    if (payload.ItemType in ExportCustoms) {
 | 
				
			||||||
 | 
					        const meta = ExportCustoms[payload.ItemType];
 | 
				
			||||||
 | 
					        let upgradeFingerprint: ICrewShipComponentFingerprint = { compat: payload.ItemType, buffs: [] };
 | 
				
			||||||
 | 
					        if (meta.subroutines) {
 | 
				
			||||||
 | 
					            upgradeFingerprint = {
 | 
				
			||||||
 | 
					                SubroutineIndex: getRandomInt(0, meta.subroutines.length - 1),
 | 
				
			||||||
 | 
					                ...upgradeFingerprint
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const upgrade of meta.randomisedUpgrades!) {
 | 
				
			||||||
 | 
					            upgradeFingerprint.buffs.push({ Tag: upgrade.tag, Value: Math.trunc(Math.random() * 0x40000000) });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        addCrewShipSalvagedWeaponSkin(
 | 
				
			||||||
 | 
					            inventory,
 | 
				
			||||||
 | 
					            payload.ItemType,
 | 
				
			||||||
 | 
					            JSON.stringify(upgradeFingerprint),
 | 
				
			||||||
 | 
					            inventoryChanges
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const meta = ExportRailjackWeapons[payload.ItemType];
 | 
				
			||||||
 | 
					        const upgradeType = meta.defaultUpgrades![0].ItemType;
 | 
				
			||||||
 | 
					        const upgradeMeta = ExportUpgrades[upgradeType];
 | 
				
			||||||
 | 
					        const buffs: IFingerprintStat[] = [];
 | 
				
			||||||
 | 
					        for (const buff of upgradeMeta.upgradeEntries!) {
 | 
				
			||||||
 | 
					            buffs.push({
 | 
				
			||||||
 | 
					                Tag: buff.tag,
 | 
				
			||||||
 | 
					                Value: Math.trunc(Math.random() * 0x40000000)
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, undefined, inventoryChanges, {
 | 
				
			||||||
 | 
					            UpgradeType: upgradeType,
 | 
				
			||||||
 | 
					            UpgradeFingerprint: JSON.stringify({
 | 
				
			||||||
 | 
					                compat: payload.ItemType,
 | 
				
			||||||
 | 
					                buffs
 | 
				
			||||||
 | 
					            } satisfies IInnateDamageFingerprint)
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inventoryChanges.CrewShipRawSalvage = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ItemType: payload.ItemType,
 | 
				
			||||||
 | 
					            ItemCount: -1
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    addCrewShipRawSalvage(inventory, inventoryChanges.CrewShipRawSalvage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ICrewShipIdentifySalvageRequest {
 | 
				
			||||||
 | 
					    ItemType: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										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();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										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();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { GuildMember } from "@/src/models/guildModel";
 | 
					import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
 | 
				
			||||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
 | 
					import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
 | 
				
			||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
					import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
@ -36,10 +36,10 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    if (request.DecoId) {
 | 
					    if (request.DecoId) {
 | 
				
			||||||
        const deco = component.Decos!.find(x => x._id.equals(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)!;
 | 
					        const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
 | 
				
			||||||
        processContribution(deco, meta, platinumDonated);
 | 
					        processContribution(guild, deco, meta, platinumDonated);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
 | 
					        const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
 | 
				
			||||||
        processContribution(component, meta, platinumDonated);
 | 
					        processContribution(guild, component, meta, platinumDonated);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
 | 
					        const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
 | 
				
			||||||
        if (entry) {
 | 
					        if (entry) {
 | 
				
			||||||
@ -47,13 +47,11 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					 | 
				
			||||||
    await inventory.save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
 | 
					    const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
 | 
				
			||||||
    guildMember.PremiumCreditsContributed ??= 0;
 | 
					    guildMember.PremiumCreditsContributed ??= 0;
 | 
				
			||||||
    guildMember.PremiumCreditsContributed += request.Amount;
 | 
					    guildMember.PremiumCreditsContributed += request.Amount;
 | 
				
			||||||
    await guildMember.save();
 | 
					
 | 
				
			||||||
 | 
					    await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        ...(await getDojoClient(guild, 0, component._id)),
 | 
					        ...(await getDojoClient(guild, 0, component._id)),
 | 
				
			||||||
@ -61,8 +59,13 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const processContribution = (component: IDojoContributable, meta: IDojoBuild, platinumDonated: number): void => {
 | 
					const processContribution = (
 | 
				
			||||||
    const fullPlatinumCost = scaleRequiredCount(meta.skipTimePrice);
 | 
					    guild: TGuildDatabaseDocument,
 | 
				
			||||||
 | 
					    component: IDojoContributable,
 | 
				
			||||||
 | 
					    meta: IDojoBuild,
 | 
				
			||||||
 | 
					    platinumDonated: number
 | 
				
			||||||
 | 
					): void => {
 | 
				
			||||||
 | 
					    const fullPlatinumCost = scaleRequiredCount(guild.Tier, meta.skipTimePrice);
 | 
				
			||||||
    const fullDurationSeconds = meta.time;
 | 
					    const fullDurationSeconds = meta.time;
 | 
				
			||||||
    const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
 | 
					    const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
 | 
				
			||||||
    component.CompletionTime = new Date(
 | 
					    component.CompletionTime = new Date(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,11 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Arbiter Dojo endpoints, not really used by us as we don't provide a ContentURL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const dojoController: RequestHandler = (_req, res) => {
 | 
					export const dojoController: RequestHandler = (_req, res) => {
 | 
				
			||||||
    res.json("-1"); // Tell client to use authorised request.
 | 
					    res.json("-1"); // Tell client to use authorised request.
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setDojoURLController: RequestHandler = (_req, res) => {
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -55,7 +55,7 @@ export const dronesController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            ? new Date()
 | 
					            ? new Date()
 | 
				
			||||||
            : new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
 | 
					            : new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
 | 
				
			||||||
        drone.PendingDamage =
 | 
					        drone.PendingDamage =
 | 
				
			||||||
            Math.random() < system.damageChance
 | 
					            !config.noResourceExtractorDronesDamage && Math.random() < system.damageChance
 | 
				
			||||||
                ? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
 | 
					                ? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
 | 
				
			||||||
                : 0;
 | 
					                : 0;
 | 
				
			||||||
        const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
 | 
					        const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
 | 
				
			||||||
 | 
				
			|||||||
@ -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,9 @@
 | 
				
			|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
 | 
					import { addMiscItems, addStanding, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { addMiscItems, getInventory, getStandingLimit, updateStandingLimit } from "@/src/services/inventoryService";
 | 
					 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportResources, ExportSyndicates } from "warframe-public-export-plus";
 | 
					import { ExportResources } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const fishmongerController: RequestHandler = async (req, res) => {
 | 
					export const fishmongerController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -31,32 +30,15 @@ export const fishmongerController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
 | 
					        miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    addMiscItems(inventory, miscItemChanges);
 | 
					    addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
    if (gainedStanding && syndicateTag) {
 | 
					    let affiliationMod;
 | 
				
			||||||
        let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
 | 
					    if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
 | 
				
			||||||
        if (!syndicate) {
 | 
					 | 
				
			||||||
            syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: syndicateTag, Standing: 0 }) - 1];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const syndicateMeta = ExportSyndicates[syndicateTag];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
 | 
					 | 
				
			||||||
        if (syndicate.Standing + gainedStanding > max) {
 | 
					 | 
				
			||||||
            gainedStanding = max - syndicate.Standing;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
 | 
					 | 
				
			||||||
            gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        syndicate.Standing += gainedStanding;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        InventoryChanges: {
 | 
					        InventoryChanges: {
 | 
				
			||||||
            MiscItems: miscItemChanges
 | 
					            MiscItems: miscItemChanges
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        SyndicateTag: syndicateTag,
 | 
					        SyndicateTag: syndicateTag,
 | 
				
			||||||
        StandingChange: gainedStanding
 | 
					        StandingChange: affiliationMod?.Standing || 0
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -18,8 +18,8 @@ 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, [
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
@ -27,8 +27,6 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                        ItemCount: -1
 | 
					                        ItemCount: -1
 | 
				
			||||||
                    } satisfies IMiscItem
 | 
					                    } satisfies IMiscItem
 | 
				
			||||||
                ]);
 | 
					                ]);
 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json({
 | 
					            res.json({
 | 
				
			||||||
 | 
				
			|||||||
@ -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,6 +1,7 @@
 | 
				
			|||||||
import { GuildMember } from "@/src/models/guildModel";
 | 
					import { GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
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 { IGuildMemberClient } from "@/src/types/guildTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getGuildContributionsController: RequestHandler = async (req, res) => {
 | 
					export const getGuildContributionsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
@ -8,11 +9,11 @@ export const getGuildContributionsController: RequestHandler = async (req, res)
 | 
				
			|||||||
    const guildId = (await getInventory(accountId, "GuildId")).GuildId;
 | 
					    const guildId = (await getInventory(accountId, "GuildId")).GuildId;
 | 
				
			||||||
    const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
 | 
					    const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        _id: { $oid: req.query.buddyId },
 | 
					        _id: { $oid: req.query.buddyId as string },
 | 
				
			||||||
        RegularCreditsContributed: guildMember.RegularCreditsContributed,
 | 
					        RegularCreditsContributed: guildMember.RegularCreditsContributed,
 | 
				
			||||||
        PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
 | 
					        PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
 | 
				
			||||||
        MiscItemsContributed: guildMember.MiscItemsContributed,
 | 
					        MiscItemsContributed: guildMember.MiscItemsContributed,
 | 
				
			||||||
        ConsumablesContributed: [], // ???
 | 
					        ConsumablesContributed: [], // ???
 | 
				
			||||||
        ShipDecorationsContributed: guildMember.ShipDecorationsContributed
 | 
					        ShipDecorationsContributed: guildMember.ShipDecorationsContributed
 | 
				
			||||||
    });
 | 
					    } satisfies Partial<IGuildMemberClient>);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ import { logger } from "@/src/utils/logger";
 | 
				
			|||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
 | 
					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 getInventory(accountId, "GuildId");
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
    if (inventory.GuildId) {
 | 
					    if (inventory.GuildId) {
 | 
				
			||||||
@ -28,7 +28,5 @@ const getGuildController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.sendStatus(200);
 | 
					    res.end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { getGuildController };
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,20 @@
 | 
				
			|||||||
 | 
					import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { Account, Ignore } from "@/src/models/loginModel";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IFriendInfo } from "@/src/types/guildTypes";
 | 
				
			||||||
 | 
					import { parallelForeach } from "@/src/utils/async-utils";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getIgnoredUsersController: RequestHandler = (_req, res) => {
 | 
					export const getIgnoredUsersController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    res.writeHead(200, {
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
        "Content-Type": "text/html",
 | 
					    const ignores = await Ignore.find({ ignorer: accountId });
 | 
				
			||||||
        "Content-Length": "3"
 | 
					    const ignoredUsers: IFriendInfo[] = [];
 | 
				
			||||||
 | 
					    await parallelForeach(ignores, async ignore => {
 | 
				
			||||||
 | 
					        const ignoreeAccount = (await Account.findById(ignore.ignoree, "DisplayName"))!;
 | 
				
			||||||
 | 
					        ignoredUsers.push({
 | 
				
			||||||
 | 
					            _id: toOid(ignore.ignoree),
 | 
				
			||||||
 | 
					            DisplayName: ignoreeAccount.DisplayName + ""
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    res.end(
 | 
					    });
 | 
				
			||||||
        Buffer.from([
 | 
					    res.json({ IgnoredUsers: ignoredUsers });
 | 
				
			||||||
            0x7b, 0x22, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x38, 0x33, 0x30, 0x34, 0x30, 0x37, 0x37, 0x32, 0x32,
 | 
					 | 
				
			||||||
            0x34, 0x30, 0x32, 0x32, 0x32, 0x36, 0x31, 0x35, 0x30, 0x31, 0x7d
 | 
					 | 
				
			||||||
        ])
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { getIgnoredUsersController };
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +1,12 @@
 | 
				
			|||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					import { generateRewardSeed } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
 | 
					export const getNewRewardSeedController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const rewardSeed = generateRewardSeed();
 | 
					    const rewardSeed = generateRewardSeed();
 | 
				
			||||||
    logger.debug(`generated new reward seed: ${rewardSeed}`);
 | 
					 | 
				
			||||||
    await Inventory.updateOne(
 | 
					    await Inventory.updateOne(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            accountOwnerId: accountId
 | 
					            accountOwnerId: accountId
 | 
				
			||||||
@ -18,9 +17,3 @@ export const getNewRewardSeedController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
    res.json({ rewardSeed: rewardSeed });
 | 
					    res.json({ rewardSeed: rewardSeed });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export function generateRewardSeed(): number {
 | 
					 | 
				
			||||||
    const min = -Number.MAX_SAFE_INTEGER;
 | 
					 | 
				
			||||||
    const max = Number.MAX_SAFE_INTEGER;
 | 
					 | 
				
			||||||
    return Math.floor(Math.random() * (max - min + 1)) + min;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,10 @@ export const getShipController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                Colors: personalRooms.ShipInteriorColors,
 | 
					                Colors: personalRooms.ShipInteriorColors,
 | 
				
			||||||
                ShipAttachments: ship.ShipAttachments,
 | 
					                ShipAttachments: ship.ShipAttachments,
 | 
				
			||||||
                SkinFlavourItem: ship.SkinFlavourItem
 | 
					                SkinFlavourItem: ship.SkinFlavourItem
 | 
				
			||||||
            }
 | 
					            },
 | 
				
			||||||
 | 
					            FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
 | 
				
			||||||
 | 
					                ? toOid(personalRooms.Ship.FavouriteLoadoutId)
 | 
				
			||||||
 | 
					                : undefined
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        Apartment: personalRooms.Apartment,
 | 
					        Apartment: personalRooms.Apartment,
 | 
				
			||||||
        TailorShop: personalRooms.TailorShop
 | 
					        TailorShop: personalRooms.TailorShop
 | 
				
			||||||
 | 
				
			|||||||
@ -56,7 +56,7 @@ export const giftingController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    await senderInventory.save();
 | 
					    await senderInventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const senderName = getSuffixedName(senderAccount);
 | 
					    const senderName = getSuffixedName(senderAccount);
 | 
				
			||||||
    await createMessage(account._id.toString(), [
 | 
					    await createMessage(account._id, [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            sndr: senderName,
 | 
					            sndr: senderName,
 | 
				
			||||||
            msg: data.Message || "/Lotus/Language/Menu/GiftReceivedBody_NoCustomMessage",
 | 
					            msg: data.Message || "/Lotus/Language/Menu/GiftReceivedBody_NoCustomMessage",
 | 
				
			||||||
 | 
				
			|||||||
@ -2,36 +2,25 @@ 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 { addMiscItems, getInventory } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { WeaponTypeInternal } from "@/src/services/itemDataService";
 | 
					import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import { ExportRecipes } from "warframe-public-export-plus";
 | 
					import { ExportRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const modularWeaponCategory: (WeaponTypeInternal | "Hoverboards")[] = [
 | 
					 | 
				
			||||||
    "LongGuns",
 | 
					 | 
				
			||||||
    "Pistols",
 | 
					 | 
				
			||||||
    "Melee",
 | 
					 | 
				
			||||||
    "OperatorAmps",
 | 
					 | 
				
			||||||
    "Hoverboards"
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IGildWeaponRequest {
 | 
					interface IGildWeaponRequest {
 | 
				
			||||||
    ItemName: string;
 | 
					    ItemName: string;
 | 
				
			||||||
    Recipe: string; // e.g. /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: TEquipmentKey;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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));
 | 
				
			||||||
    data.ItemId = String(req.query.ItemId);
 | 
					    data.ItemId = String(req.query.ItemId);
 | 
				
			||||||
    if (!modularWeaponCategory.includes(req.query.Category as WeaponTypeInternal | "Hoverboards")) {
 | 
					    data.Category = req.query.Category as TEquipmentKey;
 | 
				
			||||||
        throw new Error(`Unknown modular weapon Category: ${String(req.query.Category)}`);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    data.Category = req.query.Category as WeaponTypeInternal | "Hoverboards";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
 | 
					    const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
 | 
				
			||||||
@ -42,8 +31,10 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const weapon = inventory[data.Category][weaponIndex];
 | 
					    const weapon = inventory[data.Category][weaponIndex];
 | 
				
			||||||
    weapon.Features ??= 0;
 | 
					    weapon.Features ??= 0;
 | 
				
			||||||
    weapon.Features |= EquipmentFeatures.GILDED;
 | 
					    weapon.Features |= EquipmentFeatures.GILDED;
 | 
				
			||||||
 | 
					    if (data.Recipe != "webui") {
 | 
				
			||||||
        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) {
 | 
				
			||||||
        weapon.Polarity = [
 | 
					        weapon.Polarity = [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -56,6 +47,9 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const inventoryChanges: IInventoryChanges = {};
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
    inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
 | 
					    inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const affiliationMods = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (data.Recipe != "webui") {
 | 
				
			||||||
        const recipe = ExportRecipes[data.Recipe];
 | 
					        const recipe = ExportRecipes[data.Recipe];
 | 
				
			||||||
        inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
 | 
					        inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
 | 
				
			||||||
            ItemType: ingredient.ItemType,
 | 
					            ItemType: ingredient.ItemType,
 | 
				
			||||||
@ -63,7 +57,6 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }));
 | 
					        }));
 | 
				
			||||||
        addMiscItems(inventory, inventoryChanges.MiscItems);
 | 
					        addMiscItems(inventory, inventoryChanges.MiscItems);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const affiliationMods = [];
 | 
					 | 
				
			||||||
        if (recipe.syndicateStandingChange) {
 | 
					        if (recipe.syndicateStandingChange) {
 | 
				
			||||||
            const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
 | 
					            const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
 | 
				
			||||||
            affiliation.Standing += recipe.syndicateStandingChange.value;
 | 
					            affiliation.Standing += recipe.syndicateStandingChange.value;
 | 
				
			||||||
@ -72,6 +65,7 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                Standing: recipe.syndicateStandingChange.value
 | 
					                Standing: recipe.syndicateStandingChange.value
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
 | 
				
			|||||||
@ -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) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -16,15 +16,15 @@ export const giveQuestKeyRewardController: RequestHandler = async (req, res) =>
 | 
				
			|||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
 | 
					    const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json(inventoryChanges.InventoryChanges);
 | 
					    res.json(inventoryChanges);
 | 
				
			||||||
    //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;
 | 
				
			||||||
							
								
								
									
										20
									
								
								src/controllers/api/giveShipDecoAndLoreFragmentController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/controllers/api/giveShipDecoAndLoreFragmentController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "LoreFragmentScans ShipDecorations");
 | 
				
			||||||
 | 
					    const data = getJSONfromString<IGiveShipDecoAndLoreFragmentRequest>(String(req.body));
 | 
				
			||||||
 | 
					    addLoreFragmentScans(inventory, data.LoreFragmentScans);
 | 
				
			||||||
 | 
					    addShipDecorations(inventory, data.ShipDecorations);
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IGiveShipDecoAndLoreFragmentRequest {
 | 
				
			||||||
 | 
					    LoreFragmentScans: ILoreFragmentScan[];
 | 
				
			||||||
 | 
					    ShipDecorations: ITypeCount[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,20 +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,
 | 
					 | 
				
			||||||
    addPowerSuit,
 | 
					 | 
				
			||||||
    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);
 | 
				
			||||||
@ -26,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);
 | 
					 | 
				
			||||||
    addPowerSuit(inventory, Suits[0].ItemType, inventoryChanges);
 | 
					 | 
				
			||||||
    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);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    inventory.PlayedParkourTutorial = true;
 | 
					 | 
				
			||||||
    inventory.ReceivedStartingGear = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return inventoryChanges;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,38 +1,46 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    addGuildMemberMiscItemContribution,
 | 
				
			||||||
    getGuildForRequestEx,
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
    getGuildVault,
 | 
					    getGuildVault,
 | 
				
			||||||
    hasAccessToDojo,
 | 
					    hasAccessToDojo,
 | 
				
			||||||
    hasGuildPermission,
 | 
					    hasGuildPermission,
 | 
				
			||||||
 | 
					    processFundedGuildTechProject,
 | 
				
			||||||
 | 
					    processGuildTechProjectContributionsUpdate,
 | 
				
			||||||
    removePigmentsFromGuildMembers,
 | 
					    removePigmentsFromGuildMembers,
 | 
				
			||||||
    scaleRequiredCount
 | 
					    scaleRequiredCount,
 | 
				
			||||||
 | 
					    setGuildTechLogState
 | 
				
			||||||
} from "@/src/services/guildService";
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
import { ExportDojoRecipes, IDojoResearch } 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 {
 | 
					import {
 | 
				
			||||||
 | 
					    addCrewShipWeaponSkin,
 | 
				
			||||||
 | 
					    addEquipment,
 | 
				
			||||||
    addItem,
 | 
					    addItem,
 | 
				
			||||||
    addMiscItems,
 | 
					    addMiscItems,
 | 
				
			||||||
    addRecipes,
 | 
					    addRecipes,
 | 
				
			||||||
    combineInventoryChanges,
 | 
					    combineInventoryChanges,
 | 
				
			||||||
    getInventory,
 | 
					    getInventory,
 | 
				
			||||||
 | 
					    occupySlot,
 | 
				
			||||||
    updateCurrency
 | 
					    updateCurrency
 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
import { GuildPermission, ITechProjectClient, ITechProjectDatabase } from "@/src/types/guildTypes";
 | 
					import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
 | 
				
			||||||
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
 | 
					import { GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
					import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 data = JSON.parse(String(req.body)) as TGuildTechRequest;
 | 
					    const data = JSON.parse(String(req.body)) as TGuildTechRequest;
 | 
				
			||||||
    if (data.Action == "Sync") {
 | 
					    if (data.Action == "Sync") {
 | 
				
			||||||
        let needSave = false;
 | 
					        let needSave = false;
 | 
				
			||||||
        const techProjects: ITechProjectClient[] = [];
 | 
					        const techProjects: ITechProjectClient[] = [];
 | 
				
			||||||
 | 
					        const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
        if (guild.TechProjects) {
 | 
					        if (guild.TechProjects) {
 | 
				
			||||||
            for (const project of guild.TechProjects) {
 | 
					            for (const project of guild.TechProjects) {
 | 
				
			||||||
                const techProject: ITechProjectClient = {
 | 
					                const techProject: ITechProjectClient = {
 | 
				
			||||||
@ -44,7 +52,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                if (project.CompletionDate) {
 | 
					                if (project.CompletionDate) {
 | 
				
			||||||
                    techProject.CompletionDate = toMongoDate(project.CompletionDate);
 | 
					                    techProject.CompletionDate = toMongoDate(project.CompletionDate);
 | 
				
			||||||
                    if (Date.now() >= project.CompletionDate.getTime()) {
 | 
					                    if (Date.now() >= project.CompletionDate.getTime()) {
 | 
				
			||||||
                        needSave ||= setTechLogState(guild, project.ItemType, 4, project.CompletionDate);
 | 
					                        needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                techProjects.push(techProject);
 | 
					                techProjects.push(techProject);
 | 
				
			||||||
@ -55,6 +63,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        res.json({ TechProjects: techProjects });
 | 
					        res.json({ TechProjects: techProjects });
 | 
				
			||||||
    } else if (data.Action == "Start") {
 | 
					    } else if (data.Action == "Start") {
 | 
				
			||||||
 | 
					        if (data.Mode == "Guild") {
 | 
				
			||||||
 | 
					            const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
            if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
					            if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
                res.status(400).send("-1").end();
 | 
					                res.status(400).send("-1").end();
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
@ -66,17 +76,17 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                    guild.TechProjects[
 | 
					                    guild.TechProjects[
 | 
				
			||||||
                        guild.TechProjects.push({
 | 
					                        guild.TechProjects.push({
 | 
				
			||||||
                            ItemType: data.RecipeType,
 | 
					                            ItemType: data.RecipeType,
 | 
				
			||||||
                        ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price),
 | 
					                            ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
 | 
				
			||||||
                            ReqItems: recipe.ingredients.map(x => ({
 | 
					                            ReqItems: recipe.ingredients.map(x => ({
 | 
				
			||||||
                                ItemType: x.ItemType,
 | 
					                                ItemType: x.ItemType,
 | 
				
			||||||
                            ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(x.ItemCount)
 | 
					                                ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
 | 
				
			||||||
                            })),
 | 
					                            })),
 | 
				
			||||||
                            State: 0
 | 
					                            State: 0
 | 
				
			||||||
                        }) - 1
 | 
					                        }) - 1
 | 
				
			||||||
                    ];
 | 
					                    ];
 | 
				
			||||||
            setTechLogState(guild, techProject.ItemType, 5);
 | 
					                setGuildTechLogState(guild, techProject.ItemType, 5);
 | 
				
			||||||
                if (config.noDojoResearchCosts) {
 | 
					                if (config.noDojoResearchCosts) {
 | 
				
			||||||
                processFundedProject(guild, techProject, recipe);
 | 
					                    processFundedGuildTechProject(guild, techProject, recipe);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
 | 
					                    if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
 | 
				
			||||||
                        guild.ActiveDojoColorResearch = data.RecipeType;
 | 
					                        guild.ActiveDojoColorResearch = data.RecipeType;
 | 
				
			||||||
@ -85,38 +95,109 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            await guild.save();
 | 
					            await guild.save();
 | 
				
			||||||
            res.end();
 | 
					            res.end();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const recipe = ExportDojoRecipes.research[data.RecipeType];
 | 
				
			||||||
 | 
					            if (data.TechProductCategory) {
 | 
				
			||||||
 | 
					                if (
 | 
				
			||||||
 | 
					                    data.TechProductCategory != "CrewShipWeapons" &&
 | 
				
			||||||
 | 
					                    data.TechProductCategory != "CrewShipWeaponSkins"
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId)) {
 | 
				
			||||||
 | 
					                    throw new Error(
 | 
				
			||||||
 | 
					                        `no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const techProject =
 | 
				
			||||||
 | 
					                inventory.PersonalTechProjects[
 | 
				
			||||||
 | 
					                    inventory.PersonalTechProjects.push({
 | 
				
			||||||
 | 
					                        State: 0,
 | 
				
			||||||
 | 
					                        ReqCredits: recipe.price,
 | 
				
			||||||
 | 
					                        ItemType: data.RecipeType,
 | 
				
			||||||
 | 
					                        ProductCategory: data.TechProductCategory,
 | 
				
			||||||
 | 
					                        CategoryItemId: data.CategoryItemId,
 | 
				
			||||||
 | 
					                        ReqItems: recipe.ingredients
 | 
				
			||||||
 | 
					                    }) - 1
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					            await inventory.save();
 | 
				
			||||||
 | 
					            res.json({
 | 
				
			||||||
 | 
					                isPersonal: true,
 | 
				
			||||||
 | 
					                action: "Start",
 | 
				
			||||||
 | 
					                personalTech: techProject.toJSON()
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    } else if (data.Action == "Contribute") {
 | 
					    } else if (data.Action == "Contribute") {
 | 
				
			||||||
 | 
					        if ((req.query.guildId as string) == "000000000000000000000000") {
 | 
				
			||||||
 | 
					            const techProject = inventory.PersonalTechProjects.id(data.ResearchId)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            techProject.ReqCredits -= data.RegularCredits;
 | 
				
			||||||
 | 
					            const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const miscItemChanges = [];
 | 
				
			||||||
 | 
					            for (const miscItem of data.MiscItems) {
 | 
				
			||||||
 | 
					                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;
 | 
				
			||||||
 | 
					                    miscItemChanges.push({
 | 
				
			||||||
 | 
					                        ItemType: miscItem.ItemType,
 | 
				
			||||||
 | 
					                        ItemCount: miscItem.ItemCount * -1
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
 | 
					            inventoryChanges.MiscItems = miscItemChanges;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            techProject.HasContributions = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
 | 
				
			||||||
 | 
					                techProject.State = 1;
 | 
				
			||||||
 | 
					                const recipe = ExportDojoRecipes.research[techProject.ItemType];
 | 
				
			||||||
 | 
					                techProject.CompletionDate = new Date(Date.now() + recipe.time * 1000);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await inventory.save();
 | 
				
			||||||
 | 
					            res.json({
 | 
				
			||||||
 | 
					                InventoryChanges: inventoryChanges,
 | 
				
			||||||
 | 
					                PersonalResearch: { $oid: data.ResearchId },
 | 
				
			||||||
 | 
					                PersonalResearchDate: techProject.CompletionDate ? toMongoDate(techProject.CompletionDate) : undefined
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
            if (!hasAccessToDojo(inventory)) {
 | 
					            if (!hasAccessToDojo(inventory)) {
 | 
				
			||||||
                res.status(400).send("-1").end();
 | 
					                res.status(400).send("-1").end();
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
            const guildMember = (await GuildMember.findOne(
 | 
					            const guildMember = (await GuildMember.findOne(
 | 
				
			||||||
                { accountId, guildId: guild._id },
 | 
					                { accountId, guildId: guild._id },
 | 
				
			||||||
                "RegularCreditsContributed MiscItemsContributed"
 | 
					                "RegularCreditsContributed MiscItemsContributed"
 | 
				
			||||||
            ))!;
 | 
					            ))!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const contributions = data;
 | 
					            const techProject = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
 | 
				
			||||||
        const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (contributions.VaultCredits) {
 | 
					            if (data.VaultCredits) {
 | 
				
			||||||
            if (contributions.VaultCredits > techProject.ReqCredits) {
 | 
					                if (data.VaultCredits > techProject.ReqCredits) {
 | 
				
			||||||
                contributions.VaultCredits = techProject.ReqCredits;
 | 
					                    data.VaultCredits = techProject.ReqCredits;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            techProject.ReqCredits -= contributions.VaultCredits;
 | 
					                techProject.ReqCredits -= data.VaultCredits;
 | 
				
			||||||
            guild.VaultRegularCredits! -= contributions.VaultCredits;
 | 
					                guild.VaultRegularCredits! -= data.VaultCredits;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (contributions.RegularCredits > techProject.ReqCredits) {
 | 
					            if (data.RegularCredits > techProject.ReqCredits) {
 | 
				
			||||||
            contributions.RegularCredits = techProject.ReqCredits;
 | 
					                data.RegularCredits = techProject.ReqCredits;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        techProject.ReqCredits -= contributions.RegularCredits;
 | 
					            techProject.ReqCredits -= data.RegularCredits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            guildMember.RegularCreditsContributed ??= 0;
 | 
					            guildMember.RegularCreditsContributed ??= 0;
 | 
				
			||||||
        guildMember.RegularCreditsContributed += contributions.RegularCredits;
 | 
					            guildMember.RegularCreditsContributed += data.RegularCredits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (contributions.VaultMiscItems.length) {
 | 
					            if (data.VaultMiscItems.length) {
 | 
				
			||||||
            for (const miscItem of contributions.VaultMiscItems) {
 | 
					                for (const miscItem of data.VaultMiscItems) {
 | 
				
			||||||
                    const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
 | 
					                    const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
 | 
				
			||||||
                    if (reqItem) {
 | 
					                    if (reqItem) {
 | 
				
			||||||
                        if (miscItem.ItemCount > reqItem.ItemCount) {
 | 
					                        if (miscItem.ItemCount > reqItem.ItemCount) {
 | 
				
			||||||
@ -131,7 +212,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const miscItemChanges = [];
 | 
					            const miscItemChanges = [];
 | 
				
			||||||
        for (const miscItem of contributions.MiscItems) {
 | 
					            for (const miscItem of data.MiscItems) {
 | 
				
			||||||
                const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
 | 
					                const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
 | 
				
			||||||
                if (reqItem) {
 | 
					                if (reqItem) {
 | 
				
			||||||
                    if (miscItem.ItemCount > reqItem.ItemCount) {
 | 
					                    if (miscItem.ItemCount > reqItem.ItemCount) {
 | 
				
			||||||
@ -143,37 +224,33 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                        ItemCount: miscItem.ItemCount * -1
 | 
					                        ItemCount: miscItem.ItemCount * -1
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                guildMember.MiscItemsContributed ??= [];
 | 
					                    addGuildMemberMiscItemContribution(guildMember, miscItem);
 | 
				
			||||||
                guildMember.MiscItemsContributed.push(miscItem);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            addMiscItems(inventory, miscItemChanges);
 | 
					            addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
        const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
 | 
					            const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
 | 
				
			||||||
            inventoryChanges.MiscItems = miscItemChanges;
 | 
					            inventoryChanges.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);
 | 
				
			||||||
            const recipe = ExportDojoRecipes.research[data.RecipeType];
 | 
					 | 
				
			||||||
            processFundedProject(guild, techProject, recipe);
 | 
					 | 
				
			||||||
            if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
 | 
					 | 
				
			||||||
                guild.ActiveDojoColorResearch = "";
 | 
					 | 
				
			||||||
                await removePigmentsFromGuildMembers(guild._id);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await guild.save();
 | 
					            await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
 | 
				
			||||||
        await inventory.save();
 | 
					 | 
				
			||||||
        await guildMember.save();
 | 
					 | 
				
			||||||
            res.json({
 | 
					            res.json({
 | 
				
			||||||
                InventoryChanges: inventoryChanges,
 | 
					                InventoryChanges: inventoryChanges,
 | 
				
			||||||
                Vault: getGuildVault(guild)
 | 
					                Vault: getGuildVault(guild)
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    } else if (data.Action.split(",")[0] == "Buy") {
 | 
					    } else if (data.Action.split(",")[0] == "Buy") {
 | 
				
			||||||
        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
 | 
					        const purchase = data as IGuildTechBuyRequest;
 | 
				
			||||||
 | 
					        if (purchase.Mode == "Guild") {
 | 
				
			||||||
 | 
					            const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                !hasAccessToDojo(inventory) ||
 | 
				
			||||||
 | 
					                !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
                res.status(400).send("-1").end();
 | 
					                res.status(400).send("-1").end();
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        const purchase = data as IGuildTechBuyRequest;
 | 
					 | 
				
			||||||
            const quantity = parseInt(data.Action.split(",")[1]);
 | 
					            const quantity = parseInt(data.Action.split(",")[1]);
 | 
				
			||||||
            const recipeChanges = [
 | 
					            const recipeChanges = [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -195,7 +272,15 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                    Recipes: recipeChanges
 | 
					                    Recipes: recipeChanges
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const inventoryChanges = claimSalvagedComponent(inventory, purchase.CategoryItemId!);
 | 
				
			||||||
 | 
					            await inventory.save();
 | 
				
			||||||
 | 
					            res.json({
 | 
				
			||||||
 | 
					                inventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    } else if (data.Action == "Fabricate") {
 | 
					    } else if (data.Action == "Fabricate") {
 | 
				
			||||||
 | 
					        const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
 | 
					        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
 | 
				
			||||||
            res.status(400).send("-1").end();
 | 
					            res.status(400).send("-1").end();
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@ -212,6 +297,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        // Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
 | 
					        // Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
 | 
				
			||||||
        res.json({ inventoryChanges: inventoryChanges });
 | 
					        res.json({ inventoryChanges: inventoryChanges });
 | 
				
			||||||
    } else if (data.Action == "Pause") {
 | 
					    } else if (data.Action == "Pause") {
 | 
				
			||||||
 | 
					        const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
					        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
            res.status(400).send("-1").end();
 | 
					            res.status(400).send("-1").end();
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@ -223,6 +309,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        await removePigmentsFromGuildMembers(guild._id);
 | 
					        await removePigmentsFromGuildMembers(guild._id);
 | 
				
			||||||
        res.end();
 | 
					        res.end();
 | 
				
			||||||
    } else if (data.Action == "Unpause") {
 | 
					    } else if (data.Action == "Unpause") {
 | 
				
			||||||
 | 
					        const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
					        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
            res.status(400).send("-1").end();
 | 
					            res.status(400).send("-1").end();
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@ -232,47 +319,51 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        guild.ActiveDojoColorResearch = data.RecipeType;
 | 
					        guild.ActiveDojoColorResearch = data.RecipeType;
 | 
				
			||||||
        await guild.save();
 | 
					        await guild.save();
 | 
				
			||||||
        res.end();
 | 
					        res.end();
 | 
				
			||||||
    } else {
 | 
					    } else if (data.Action == "Cancel" && data.CategoryItemId) {
 | 
				
			||||||
        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
					        const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x =>
 | 
				
			||||||
        throw new Error(`unknown guildTech action: ${data.Action}`);
 | 
					            x.CategoryItemId?.equals(data.CategoryItemId)
 | 
				
			||||||
    }
 | 
					        );
 | 
				
			||||||
};
 | 
					        const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
 | 
				
			||||||
 | 
					        inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const processFundedProject = (
 | 
					        const meta = ExportDojoRecipes.research[personalTechProject.ItemType];
 | 
				
			||||||
    guild: TGuildDatabaseDocument,
 | 
					        const contributedCredits = meta.price - personalTechProject.ReqCredits;
 | 
				
			||||||
    techProject: ITechProjectDatabase,
 | 
					        const inventoryChanges = updateCurrency(inventory, contributedCredits * -1, false);
 | 
				
			||||||
    recipe: IDojoResearch
 | 
					        inventoryChanges.MiscItems = [];
 | 
				
			||||||
): void => {
 | 
					        for (const ingredient of meta.ingredients) {
 | 
				
			||||||
    techProject.State = 1;
 | 
					            const reqItem = personalTechProject.ReqItems.find(x => x.ItemType == ingredient.ItemType);
 | 
				
			||||||
    techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
 | 
					            if (reqItem) {
 | 
				
			||||||
    if (recipe.guildXpValue) {
 | 
					                const contributedItems = ingredient.ItemCount - reqItem.ItemCount;
 | 
				
			||||||
        guild.XP += recipe.guildXpValue;
 | 
					                inventoryChanges.MiscItems.push({
 | 
				
			||||||
    }
 | 
					                    ItemType: ingredient.ItemType,
 | 
				
			||||||
    setTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
 | 
					                    ItemCount: contributedItems
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const setTechLogState = (
 | 
					 | 
				
			||||||
    guild: TGuildDatabaseDocument,
 | 
					 | 
				
			||||||
    type: string,
 | 
					 | 
				
			||||||
    state: number,
 | 
					 | 
				
			||||||
    dateTime: Date | undefined = undefined
 | 
					 | 
				
			||||||
): boolean => {
 | 
					 | 
				
			||||||
    guild.TechChanges ??= [];
 | 
					 | 
				
			||||||
    const entry = guild.TechChanges.find(x => x.details == type);
 | 
					 | 
				
			||||||
    if (entry) {
 | 
					 | 
				
			||||||
        if (entry.entryType == state) {
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        entry.dateTime = dateTime;
 | 
					 | 
				
			||||||
        entry.entryType = state;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        guild.TechChanges.push({
 | 
					 | 
				
			||||||
            dateTime: dateTime,
 | 
					 | 
				
			||||||
            entryType: state,
 | 
					 | 
				
			||||||
            details: type
 | 
					 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
    return true;
 | 
					        }
 | 
				
			||||||
 | 
					        addMiscItems(inventory, inventoryChanges.MiscItems);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            action: "Cancel",
 | 
				
			||||||
 | 
					            isPersonal: true,
 | 
				
			||||||
 | 
					            inventoryChanges: inventoryChanges,
 | 
				
			||||||
 | 
					            personalTech: {
 | 
				
			||||||
 | 
					                ItemId: toOid(personalTechProject._id)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else if (data.Action == "Rush" && data.CategoryItemId) {
 | 
				
			||||||
 | 
					        const inventoryChanges: IInventoryChanges = {
 | 
				
			||||||
 | 
					            ...updateCurrency(inventory, 20, true),
 | 
				
			||||||
 | 
					            ...claimSalvagedComponent(inventory, data.CategoryItemId)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            inventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
 | 
					        throw new Error(`unhandled guildTech request`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TGuildTechRequest =
 | 
					type TGuildTechRequest =
 | 
				
			||||||
@ -281,23 +372,69 @@ type TGuildTechRequest =
 | 
				
			|||||||
    | IGuildTechContributeRequest;
 | 
					    | IGuildTechContributeRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGuildTechBasicRequest {
 | 
					interface IGuildTechBasicRequest {
 | 
				
			||||||
    Action: "Start" | "Fabricate" | "Pause" | "Unpause";
 | 
					    Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush";
 | 
				
			||||||
    Mode: "Guild";
 | 
					    Mode: "Guild" | "Personal";
 | 
				
			||||||
    RecipeType: string;
 | 
					    RecipeType: string;
 | 
				
			||||||
 | 
					    TechProductCategory?: string;
 | 
				
			||||||
 | 
					    CategoryItemId?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGuildTechBuyRequest {
 | 
					interface IGuildTechBuyRequest extends Omit<IGuildTechBasicRequest, "Action"> {
 | 
				
			||||||
    Action: string;
 | 
					    Action: string;
 | 
				
			||||||
    Mode: "Guild";
 | 
					 | 
				
			||||||
    RecipeType: string;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGuildTechContributeRequest {
 | 
					interface IGuildTechContributeRequest {
 | 
				
			||||||
    Action: "Contribute";
 | 
					    Action: "Contribute";
 | 
				
			||||||
    ResearchId: "";
 | 
					    ResearchId: string;
 | 
				
			||||||
    RecipeType: string;
 | 
					    RecipeType: string;
 | 
				
			||||||
    RegularCredits: number;
 | 
					    RegularCredits: number;
 | 
				
			||||||
    MiscItems: IMiscItem[];
 | 
					    MiscItems: IMiscItem[];
 | 
				
			||||||
    VaultCredits: number;
 | 
					    VaultCredits: number;
 | 
				
			||||||
    VaultMiscItems: IMiscItem[];
 | 
					    VaultMiscItems: IMiscItem[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getSalvageCategory = (
 | 
				
			||||||
 | 
					    category: "CrewShipWeapons" | "CrewShipWeaponSkins"
 | 
				
			||||||
 | 
					): "CrewShipSalvagedWeapons" | "CrewShipSalvagedWeaponSkins" => {
 | 
				
			||||||
 | 
					    return category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
 | 
				
			||||||
 | 
					    // delete personal tech project
 | 
				
			||||||
 | 
					    const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId));
 | 
				
			||||||
 | 
					    const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
 | 
				
			||||||
 | 
					    inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const category = personalTechProject.ProductCategory! as "CrewShipWeapons" | "CrewShipWeaponSkins";
 | 
				
			||||||
 | 
					    const salvageCategory = getSalvageCategory(category);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // find salved part & delete it
 | 
				
			||||||
 | 
					    const salvageIndex = inventory[salvageCategory].findIndex(x => x._id.equals(itemId));
 | 
				
			||||||
 | 
					    const salvageItem = inventory[category][salvageIndex];
 | 
				
			||||||
 | 
					    inventory[salvageCategory].splice(salvageIndex, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // add final item
 | 
				
			||||||
 | 
					    const inventoryChanges = {
 | 
				
			||||||
 | 
					        ...(category == "CrewShipWeaponSkins"
 | 
				
			||||||
 | 
					            ? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
 | 
				
			||||||
 | 
					            : addEquipment(
 | 
				
			||||||
 | 
					                  inventory,
 | 
				
			||||||
 | 
					                  category,
 | 
				
			||||||
 | 
					                  salvageItem.ItemType,
 | 
				
			||||||
 | 
					                  undefined,
 | 
				
			||||||
 | 
					                  {},
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                      UpgradeFingerprint: salvageItem.UpgradeFingerprint
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					              )),
 | 
				
			||||||
 | 
					        ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inventoryChanges.RemovedIdItems = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ItemId: { $oid: itemId }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return inventoryChanges;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -34,8 +34,8 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        message.r = true;
 | 
					        message.r = true;
 | 
				
			||||||
        await message.save();
 | 
					        await message.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const attachmentItems = message.att;
 | 
					        const attachmentItems = message.attVisualOnly ? undefined : message.att;
 | 
				
			||||||
        const attachmentCountedItems = message.countedAtt;
 | 
					        const attachmentCountedItems = message.attVisualOnly ? undefined : message.countedAtt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
 | 
					        if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
 | 
				
			||||||
            res.status(200).end();
 | 
					            res.status(200).end();
 | 
				
			||||||
@ -67,7 +67,7 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                    (await handleStoreItemAcquisition(gift.GiftType, inventory, giftQuantity)).InventoryChanges
 | 
					                    (await handleStoreItemAcquisition(gift.GiftType, inventory, giftQuantity)).InventoryChanges
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                if (sender) {
 | 
					                if (sender) {
 | 
				
			||||||
                    await createMessage(sender._id.toString(), [
 | 
					                    await createMessage(sender._id, [
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            sndr: recipientName,
 | 
					                            sndr: recipientName,
 | 
				
			||||||
                            msg: "/Lotus/Language/Menu/GiftReceivedConfirmationBody",
 | 
					                            msg: "/Lotus/Language/Menu/GiftReceivedConfirmationBody",
 | 
				
			||||||
 | 
				
			|||||||
@ -6,20 +6,21 @@ import { IOid } from "@/src/types/commonTypes";
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
    IConsumedSuit,
 | 
					    IConsumedSuit,
 | 
				
			||||||
    IHelminthFoodRecord,
 | 
					    IHelminthFoodRecord,
 | 
				
			||||||
    IInfestedFoundryClient,
 | 
					 | 
				
			||||||
    IInfestedFoundryDatabase,
 | 
					 | 
				
			||||||
    IInventoryClient,
 | 
					    IInventoryClient,
 | 
				
			||||||
    IMiscItem,
 | 
					    IMiscItem,
 | 
				
			||||||
    InventorySlot,
 | 
					    InventorySlot
 | 
				
			||||||
    ITypeCount
 | 
					 | 
				
			||||||
} 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);
 | 
				
			||||||
@ -28,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 = [{}, {}, {}, {}, {}];
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -56,7 +57,7 @@ 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)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const miscItemChanges: IMiscItem[] = [];
 | 
					            const miscItemChanges: IMiscItem[] = [];
 | 
				
			||||||
            if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
 | 
					            if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
 | 
				
			||||||
@ -383,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[];
 | 
				
			||||||
 | 
				
			|||||||
@ -13,9 +13,10 @@ 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 { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					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 accountId = await getAccountIdForRequest(request);
 | 
					    const accountId = await getAccountIdForRequest(request);
 | 
				
			||||||
@ -34,6 +35,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
 | 
					        inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
 | 
				
			||||||
        inventory.GiftsRemaining = Math.max(8, inventory.PlayerLevel);
 | 
					        inventory.GiftsRemaining = Math.max(8, inventory.PlayerLevel);
 | 
				
			||||||
 | 
					        inventory.TradesRemaining = inventory.PlayerLevel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
 | 
					        inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -51,9 +53,11 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
				
			|||||||
                    if (numArgonCrystals == 0) {
 | 
					                    if (numArgonCrystals == 0) {
 | 
				
			||||||
                        break;
 | 
					                        break;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    const numStableArgonCrystals =
 | 
					                    const numStableArgonCrystals = Math.min(
 | 
				
			||||||
 | 
					                        numArgonCrystals,
 | 
				
			||||||
                        inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
 | 
					                        inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
 | 
				
			||||||
                            ?.ItemCount ?? 0;
 | 
					                            ?.ItemCount ?? 0
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
                    const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
 | 
					                    const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
 | 
				
			||||||
                    const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
 | 
					                    const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
 | 
				
			||||||
                    logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
 | 
					                    logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
 | 
				
			||||||
@ -145,7 +149,7 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
        inventoryResponse.ShipDecorations = [];
 | 
					        inventoryResponse.ShipDecorations = [];
 | 
				
			||||||
        for (const [uniqueName, item] of Object.entries(ExportResources)) {
 | 
					        for (const [uniqueName, item] of Object.entries(ExportResources)) {
 | 
				
			||||||
            if (item.productCategory == "ShipDecorations") {
 | 
					            if (item.productCategory == "ShipDecorations") {
 | 
				
			||||||
                inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 1 });
 | 
					                inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 999_999 });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -198,7 +202,8 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (config.universalPolarityEverywhere) {
 | 
					    if (config.universalPolarityEverywhere) {
 | 
				
			||||||
        const Polarity: IPolarity[] = [];
 | 
					        const Polarity: IPolarity[] = [];
 | 
				
			||||||
        for (let i = 0; i != 12; ++i) {
 | 
					        // 12 is needed for necramechs. 15 is needed for plexus/crewshipharness.
 | 
				
			||||||
 | 
					        for (let i = 0; i != 15; ++i) {
 | 
				
			||||||
            Polarity.push({
 | 
					            Polarity.push({
 | 
				
			||||||
                Slot: i,
 | 
					                Slot: i,
 | 
				
			||||||
                Value: ArtifactPolarity.Any
 | 
					                Value: ArtifactPolarity.Any
 | 
				
			||||||
@ -253,6 +258,10 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (config.noDailyFocusLimit) {
 | 
				
			||||||
 | 
					        inventoryResponse.DailyFocus = Math.max(999_999, 250000 + inventoryResponse.PlayerLevel * 5000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (inventoryResponse.InfestedFoundry) {
 | 
					    if (inventoryResponse.InfestedFoundry) {
 | 
				
			||||||
        applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
 | 
					        applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -266,7 +275,7 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
    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);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -296,13 +305,3 @@ const resourceGetParent = (resourceName: string): string | undefined => {
 | 
				
			|||||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
					    // 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.
 | 
					 | 
				
			||||||
export 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;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -54,8 +54,12 @@ 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;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,8 @@ import {
 | 
				
			|||||||
    claimLoginReward,
 | 
					    claimLoginReward,
 | 
				
			||||||
    getRandomLoginRewards,
 | 
					    getRandomLoginRewards,
 | 
				
			||||||
    ILoginRewardsReponse,
 | 
					    ILoginRewardsReponse,
 | 
				
			||||||
    isLoginRewardAChoice
 | 
					    isLoginRewardAChoice,
 | 
				
			||||||
 | 
					    setAccountGotLoginRewardToday
 | 
				
			||||||
} from "@/src/services/loginRewardService";
 | 
					} from "@/src/services/loginRewardService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,8 +45,11 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    if (!isMilestoneDay && randomRewards.length == 1) {
 | 
					    if (!isMilestoneDay && randomRewards.length == 1) {
 | 
				
			||||||
        response.DailyTributeInfo.HasChosenReward = true;
 | 
					        response.DailyTributeInfo.HasChosenReward = true;
 | 
				
			||||||
        response.DailyTributeInfo.ChosenReward = randomRewards[0];
 | 
					        response.DailyTributeInfo.ChosenReward = randomRewards[0];
 | 
				
			||||||
        response.DailyTributeInfo.NewInventory = await claimLoginReward(account, inventory, randomRewards[0]);
 | 
					        response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setAccountGotLoginRewardToday(account);
 | 
				
			||||||
 | 
					        await account.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.json(response);
 | 
					    res.json(response);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,9 @@
 | 
				
			|||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { claimLoginReward, getRandomLoginRewards } from "@/src/services/loginRewardService";
 | 
					import {
 | 
				
			||||||
 | 
					    claimLoginReward,
 | 
				
			||||||
 | 
					    getRandomLoginRewards,
 | 
				
			||||||
 | 
					    setAccountGotLoginRewardToday
 | 
				
			||||||
 | 
					} from "@/src/services/loginRewardService";
 | 
				
			||||||
import { getAccountForRequest } from "@/src/services/loginService";
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
					import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
@ -28,9 +32,13 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        const randomRewards = getRandomLoginRewards(account, inventory);
 | 
					        const randomRewards = getRandomLoginRewards(account, inventory);
 | 
				
			||||||
        chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
 | 
					        chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
 | 
				
			||||||
        inventoryChanges = await claimLoginReward(account, inventory, chosenReward);
 | 
					        inventoryChanges = await claimLoginReward(inventory, chosenReward);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setAccountGotLoginRewardToday(account);
 | 
				
			||||||
 | 
					    await account.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        DailyTributeInfo: {
 | 
					        DailyTributeInfo: {
 | 
				
			||||||
            NewInventory: inventoryChanges,
 | 
					            NewInventory: inventoryChanges,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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,16 +47,21 @@ 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 firstCompletion = missionReport.SortieId
 | 
				
			||||||
 | 
					        ? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
 | 
				
			||||||
 | 
					        : false;
 | 
				
			||||||
    const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
 | 
					    const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (missionReport.MissionStatus !== "GS_SUCCESS") {
 | 
					    if (
 | 
				
			||||||
 | 
					        missionReport.MissionStatus !== "GS_SUCCESS" &&
 | 
				
			||||||
 | 
					        !(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        const inventoryResponse = await getInventoryResponse(inventory, true);
 | 
					        const inventoryResponse = await getInventoryResponse(inventory, true);
 | 
				
			||||||
        res.json({
 | 
					        res.json({
 | 
				
			||||||
@ -66,7 +71,8 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { MissionRewards, inventoryChanges, credits } = await addMissionRewards(inventory, missionReport);
 | 
					    const { MissionRewards, inventoryChanges, credits, AffiliationMods, SyndicateXPItemReward } =
 | 
				
			||||||
 | 
					        await addMissionRewards(inventory, missionReport, firstCompletion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    const inventoryResponse = await getInventoryResponse(inventory, true);
 | 
					    const inventoryResponse = await getInventoryResponse(inventory, true);
 | 
				
			||||||
@ -78,7 +84,9 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
				
			|||||||
        MissionRewards,
 | 
					        MissionRewards,
 | 
				
			||||||
        ...credits,
 | 
					        ...credits,
 | 
				
			||||||
        ...inventoryUpdates,
 | 
					        ...inventoryUpdates,
 | 
				
			||||||
        FusionPoints: inventoryChanges?.FusionPoints
 | 
					        FusionPoints: inventoryChanges?.FusionPoints,
 | 
				
			||||||
 | 
					        SyndicateXPItemReward,
 | 
				
			||||||
 | 
					        AffiliationMods
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
 | 
				
			|||||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
					import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
				
			||||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import { getRandomInt } from "@/src/services/rngService";
 | 
					import { getRandomInt } from "@/src/services/rngService";
 | 
				
			||||||
import { ExportSentinels } from "warframe-public-export-plus";
 | 
					import { ExportSentinels, IDefaultUpgrade } from "warframe-public-export-plus";
 | 
				
			||||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IModularCraftRequest {
 | 
					interface IModularCraftRequest {
 | 
				
			||||||
@ -34,10 +34,8 @@ 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const defaultUpgrades = getDefaultUpgrades(data.Parts);
 | 
					    let defaultUpgrades: IDefaultUpgrade[] | undefined;
 | 
				
			||||||
    const defaultOverwrites: Partial<IEquipmentDatabase> = {
 | 
					    const defaultOverwrites: Partial<IEquipmentDatabase> = {};
 | 
				
			||||||
        Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    const inventoryChanges: IInventoryChanges = {};
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
    if (category == "KubrowPets") {
 | 
					    if (category == "KubrowPets") {
 | 
				
			||||||
        const traits = {
 | 
					        const traits = {
 | 
				
			||||||
@ -129,10 +127,17 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
				
			|||||||
        // Only save mutagen & antigen in the ModularParts.
 | 
					        // Only save mutagen & antigen in the ModularParts.
 | 
				
			||||||
        defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
 | 
					        defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const specialItem of ExportSentinels[data.WeaponType].exalted!) {
 | 
					        const meta = ExportSentinels[data.WeaponType];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const specialItem of meta.exalted!) {
 | 
				
			||||||
            addSpecialItem(inventory, specialItem, inventoryChanges);
 | 
					            addSpecialItem(inventory, specialItem, inventoryChanges);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        defaultUpgrades = meta.defaultUpgrades;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        defaultUpgrades = getDefaultUpgrades(data.Parts);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
 | 
				
			||||||
    addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
 | 
					    addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
 | 
				
			||||||
    combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
 | 
					    combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
 | 
				
			||||||
    if (defaultUpgrades) {
 | 
					    if (defaultUpgrades) {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,6 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const partTypeToParts: Record<string, string[]> = {};
 | 
					    const partTypeToParts: Record<string, string[]> = {};
 | 
				
			||||||
    for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
 | 
					    for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
 | 
				
			||||||
        if (data.partType && data.premiumPrice) {
 | 
					        if (data.partType && data.premiumPrice) {
 | 
				
			||||||
            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
					 | 
				
			||||||
            partTypeToParts[data.partType] ??= [];
 | 
					            partTypeToParts[data.partType] ??= [];
 | 
				
			||||||
            partTypeToParts[data.partType].push(uniqueName);
 | 
					            partTypeToParts[data.partType].push(uniqueName);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,25 @@
 | 
				
			|||||||
import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers";
 | 
					import {
 | 
				
			||||||
 | 
					    consumeModCharge,
 | 
				
			||||||
 | 
					    encodeNemesisGuess,
 | 
				
			||||||
 | 
					    getInfNodes,
 | 
				
			||||||
 | 
					    getNemesisPasscode,
 | 
				
			||||||
 | 
					    IKnifeResponse
 | 
				
			||||||
 | 
					} from "@/src/helpers/nemesisHelpers";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
				
			||||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
 | 
					import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { SRng } from "@/src/services/rngService";
 | 
					import { SRng } from "@/src/services/rngService";
 | 
				
			||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
 | 
					import { IMongoDate, IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { IInnateDamageFingerprint, InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    IInnateDamageFingerprint,
 | 
				
			||||||
 | 
					    InventorySlot,
 | 
				
			||||||
 | 
					    IUpgradeClient,
 | 
				
			||||||
 | 
					    IWeaponSkinClient,
 | 
				
			||||||
 | 
					    LoadoutIndex,
 | 
				
			||||||
 | 
					    TEquipmentKey
 | 
				
			||||||
 | 
					} from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -18,7 +33,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
 | 
					        const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
 | 
				
			||||||
        const sourceFingerprint = JSON.parse(sourceWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
 | 
					        const sourceFingerprint = JSON.parse(sourceWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Upgrade destination damage type if desireed
 | 
					        // Update destination damage type if desired
 | 
				
			||||||
        if (body.UseSourceDmgType) {
 | 
					        if (body.UseSourceDmgType) {
 | 
				
			||||||
            destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
 | 
					            destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -27,7 +42,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        const destDamage = 0.25 + (destFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
 | 
					        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);
 | 
					        const sourceDamage = 0.25 + (sourceFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
 | 
				
			||||||
        let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
 | 
					        let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
 | 
				
			||||||
        if (newDamage >= 0.58) {
 | 
					        if (newDamage >= 0.5794998) {
 | 
				
			||||||
            newDamage = 0.6;
 | 
					            newDamage = 0.6;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff);
 | 
					        destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff);
 | 
				
			||||||
@ -42,13 +57,14 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        res.json({
 | 
					        res.json({
 | 
				
			||||||
            InventoryChanges: {
 | 
					            InventoryChanges: {
 | 
				
			||||||
                [body.Category]: [destWeapon.toJSON()]
 | 
					                [body.Category]: [destWeapon.toJSON()],
 | 
				
			||||||
 | 
					                RemovedIdItems: [{ ItemId: body.SourceWeapon }]
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    } else if ((req.query.mode as string) == "p") {
 | 
					    } else if ((req.query.mode as string) == "p") {
 | 
				
			||||||
        const inventory = await getInventory(accountId, "Nemesis");
 | 
					        const inventory = await getInventory(accountId, "Nemesis");
 | 
				
			||||||
        const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
 | 
					        const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
 | 
				
			||||||
        const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction);
 | 
					        const passcode = getNemesisPasscode(inventory.Nemesis!);
 | 
				
			||||||
        let guessResult = 0;
 | 
					        let guessResult = 0;
 | 
				
			||||||
        if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
 | 
					        if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
 | 
				
			||||||
            for (let i = 0; i != 3; ++i) {
 | 
					            for (let i = 0; i != 3; ++i) {
 | 
				
			||||||
@ -65,6 +81,88 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        res.json({ GuessResult: guessResult });
 | 
					        res.json({ GuessResult: guessResult });
 | 
				
			||||||
 | 
					    } else if (req.query.mode == "r") {
 | 
				
			||||||
 | 
					        const inventory = await getInventory(
 | 
				
			||||||
 | 
					            accountId,
 | 
				
			||||||
 | 
					            "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
 | 
				
			||||||
 | 
					        if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
 | 
				
			||||||
 | 
					            const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
 | 
				
			||||||
 | 
					            const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Add to GuessHistory
 | 
				
			||||||
 | 
					            const result1 = passcode == guess[0] ? 0 : 1;
 | 
				
			||||||
 | 
					            const result2 = passcode == guess[1] ? 0 : 1;
 | 
				
			||||||
 | 
					            const result3 = passcode == guess[2] ? 0 : 1;
 | 
				
			||||||
 | 
					            inventory.Nemesis!.GuessHistory.push(
 | 
				
			||||||
 | 
					                encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Increase antivirus
 | 
				
			||||||
 | 
					            let antivirusGain = 5;
 | 
				
			||||||
 | 
					            const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
 | 
				
			||||||
 | 
					            const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
 | 
				
			||||||
 | 
					            const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
 | 
				
			||||||
 | 
					            const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
 | 
				
			||||||
 | 
					            const response: IKnifeResponse = {};
 | 
				
			||||||
 | 
					            for (const upgrade of body.knife!.AttachedUpgrades) {
 | 
				
			||||||
 | 
					                switch (upgrade.ItemType) {
 | 
				
			||||||
 | 
					                    case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
 | 
				
			||||||
 | 
					                        antivirusGain += 10;
 | 
				
			||||||
 | 
					                        consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
 | 
				
			||||||
 | 
					                        antivirusGain += 10;
 | 
				
			||||||
 | 
					                        consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
 | 
				
			||||||
 | 
					                        antivirusGain += 15;
 | 
				
			||||||
 | 
					                        consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
 | 
				
			||||||
 | 
					                        antivirusGain += 15;
 | 
				
			||||||
 | 
					                        consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
 | 
				
			||||||
 | 
					                        antivirusGain += 10;
 | 
				
			||||||
 | 
					                        consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            inventory.Nemesis!.HenchmenKilled += antivirusGain;
 | 
				
			||||||
 | 
					            if (inventory.Nemesis!.HenchmenKilled >= 100) {
 | 
				
			||||||
 | 
					                inventory.Nemesis!.HenchmenKilled = 100;
 | 
				
			||||||
 | 
					                inventory.Nemesis!.InfNodes = [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Node: "CrewBattleNode559",
 | 
				
			||||||
 | 
					                        Influence: 1
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					                inventory.Nemesis!.Weakened = true;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await inventory.save();
 | 
				
			||||||
 | 
					            res.json(response);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const passcode = getNemesisPasscode(inventory.Nemesis!);
 | 
				
			||||||
 | 
					            if (passcode[body.position] != body.guess) {
 | 
				
			||||||
 | 
					                res.end();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                inventory.Nemesis!.Rank += 1;
 | 
				
			||||||
 | 
					                inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank);
 | 
				
			||||||
 | 
					                await inventory.save();
 | 
				
			||||||
 | 
					                res.json({ RankIncrease: 1 });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if ((req.query.mode as string) == "rs") {
 | 
				
			||||||
 | 
					        // report spawn; POST but no application data in body
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId, "Nemesis");
 | 
				
			||||||
 | 
					        inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        res.json({ LastEnc: inventory.Nemesis!.LastEnc });
 | 
				
			||||||
    } else if ((req.query.mode as string) == "s") {
 | 
					    } else if ((req.query.mode as string) == "s") {
 | 
				
			||||||
        const inventory = await getInventory(accountId, "Nemesis");
 | 
					        const inventory = await getInventory(accountId, "Nemesis");
 | 
				
			||||||
        const body = getJSONfromString<INemesisStartRequest>(String(req.body));
 | 
					        const body = getJSONfromString<INemesisStartRequest>(String(req.body));
 | 
				
			||||||
@ -172,6 +270,20 @@ interface INemesisPrespawnCheckRequest {
 | 
				
			|||||||
    potency?: number[];
 | 
					    potency?: number[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface INemesisRequiemRequest {
 | 
				
			||||||
 | 
					    guess: number; // grn/crp: 4 bits | coda: 3x 4 bits
 | 
				
			||||||
 | 
					    position: number; // grn/crp: 0-2 | coda: 0
 | 
				
			||||||
 | 
					    // knife field provided for coda only
 | 
				
			||||||
 | 
					    knife?: {
 | 
				
			||||||
 | 
					        Item: IEquipmentClient;
 | 
				
			||||||
 | 
					        Skins: IWeaponSkinClient[];
 | 
				
			||||||
 | 
					        ModSlot: number;
 | 
				
			||||||
 | 
					        CustSlot: number;
 | 
				
			||||||
 | 
					        AttachedUpgrades: IUpgradeClient[];
 | 
				
			||||||
 | 
					        HiddenWhenHolstered: boolean;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const kuvaLichVersionSixWeapons = [
 | 
					const kuvaLichVersionSixWeapons = [
 | 
				
			||||||
    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
 | 
				
			||||||
    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
 | 
					    "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,19 @@
 | 
				
			|||||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
 | 
					import {
 | 
				
			||||||
 | 
					    getDojoClient,
 | 
				
			||||||
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
 | 
					    getVaultMiscItemCount,
 | 
				
			||||||
 | 
					    hasAccessToDojo,
 | 
				
			||||||
 | 
					    hasGuildPermission,
 | 
				
			||||||
 | 
					    processDojoBuildMaterialsGathered,
 | 
				
			||||||
 | 
					    scaleRequiredCount
 | 
				
			||||||
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
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 { GuildPermission } from "@/src/types/guildTypes";
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const placeDecoInComponentController: RequestHandler = async (req, res) => {
 | 
					export const placeDecoInComponentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -24,6 +33,11 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    component.Decos ??= [];
 | 
					    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 =
 | 
					        const deco =
 | 
				
			||||||
            component.Decos[
 | 
					            component.Decos[
 | 
				
			||||||
                component.Decos.push({
 | 
					                component.Decos.push({
 | 
				
			||||||
@ -31,17 +45,61 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
 | 
				
			|||||||
                    Type: request.Type,
 | 
					                    Type: request.Type,
 | 
				
			||||||
                    Pos: request.Pos,
 | 
					                    Pos: request.Pos,
 | 
				
			||||||
                    Rot: request.Rot,
 | 
					                    Rot: request.Rot,
 | 
				
			||||||
                Name: request.Name
 | 
					                    Name: request.Name,
 | 
				
			||||||
 | 
					                    Sockets: request.Sockets
 | 
				
			||||||
                }) - 1
 | 
					                }) - 1
 | 
				
			||||||
            ];
 | 
					            ];
 | 
				
			||||||
 | 
					 | 
				
			||||||
        const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == request.Type);
 | 
					        const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == request.Type);
 | 
				
			||||||
        if (meta) {
 | 
					        if (meta) {
 | 
				
			||||||
            if (meta.capacityCost) {
 | 
					            if (meta.capacityCost) {
 | 
				
			||||||
                component.DecoCapacity -= meta.capacityCost;
 | 
					                component.DecoCapacity -= meta.capacityCost;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        if (meta.price == 0 && meta.ingredients.length == 0) {
 | 
					        } 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 (deco.Type != "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco") {
 | 
				
			||||||
 | 
					            if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || config.noDojoDecoBuildStage) {
 | 
				
			||||||
                deco.CompletionTime = new Date();
 | 
					                deco.CompletionTime = new Date();
 | 
				
			||||||
 | 
					                if (meta) {
 | 
				
			||||||
 | 
					                    processDojoBuildMaterialsGathered(guild, meta);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } 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);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -56,4 +114,9 @@ interface IPlaceDecoInComponentRequest {
 | 
				
			|||||||
    Pos: number[];
 | 
					    Pos: number[];
 | 
				
			||||||
    Rot: number[];
 | 
					    Rot: number[];
 | 
				
			||||||
    Name?: string;
 | 
					    Name?: string;
 | 
				
			||||||
 | 
					    Sockets?: number;
 | 
				
			||||||
 | 
					    Scale?: number; // only provided alongside MoveId and seems to always be 1
 | 
				
			||||||
 | 
					    MoveId?: string;
 | 
				
			||||||
 | 
					    ShipDeco?: boolean;
 | 
				
			||||||
 | 
					    VaultDeco?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								src/controllers/api/playedParkourTutorialController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/controllers/api/playedParkourTutorialController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const playedParkourTutorialController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    await Inventory.updateOne({ accountOwnerId: accountId }, { PlayedParkourTutorial: true });
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -16,7 +16,7 @@ export const queueDojoComponentDestructionController: RequestHandler = async (re
 | 
				
			|||||||
    const componentId = req.query.componentId as string;
 | 
					    const componentId = req.query.componentId as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    guild.DojoComponents.id(componentId)!.DestructionTime = new Date(
 | 
					    guild.DojoComponents.id(componentId)!.DestructionTime = new Date(
 | 
				
			||||||
        Date.now() + (config.fastDojoRoomDestruction ? 5_000 : 2 * 3600_000)
 | 
					        (Math.trunc(Date.now() / 1000) + (config.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
import { GuildMember } from "@/src/models/guildModel";
 | 
					import { GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
 | 
					import { Inbox } from "@/src/models/inboxModel";
 | 
				
			||||||
import { Account } from "@/src/models/loginModel";
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
 | 
					import { deleteGuild, getGuildForRequest, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { createMessage } from "@/src/services/inboxService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
					import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
import { GuildPermission } from "@/src/types/guildTypes";
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
@ -17,26 +19,41 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const guildMember = (await GuildMember.findOne({ accountId: payload.userId, guildId: guild._id }))!;
 | 
					    const guildMember = (await GuildMember.findOne({ accountId: payload.userId, guildId: guild._id }))!;
 | 
				
			||||||
    if (guildMember.status == 0) {
 | 
					    if (guildMember.rank == 0) {
 | 
				
			||||||
        const inventory = await getInventory(payload.userId);
 | 
					        await deleteGuild(guild._id);
 | 
				
			||||||
        inventory.GuildId = undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Remove clan key or blueprint from kicked member
 | 
					 | 
				
			||||||
        const itemIndex = inventory.LevelKeys.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKey");
 | 
					 | 
				
			||||||
        if (itemIndex != -1) {
 | 
					 | 
				
			||||||
            inventory.LevelKeys.splice(itemIndex, 1);
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
            const recipeIndex = inventory.Recipes.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKeyBlueprint");
 | 
					        if (guildMember.status == 0) {
 | 
				
			||||||
            if (recipeIndex != -1) {
 | 
					            const inventory = await getInventory(payload.userId, "GuildId LevelKeys Recipes");
 | 
				
			||||||
                inventory.Recipes.splice(itemIndex, 1);
 | 
					            inventory.GuildId = undefined;
 | 
				
			||||||
            }
 | 
					            removeDojoKeyItems(inventory);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
 | 
					        } else if (guildMember.status == 1) {
 | 
				
			||||||
        // TODO: Handle clan leader kicking themselves (guild should be deleted in this case, I think)
 | 
					            // 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) {
 | 
					        } else if (guildMember.status == 2) {
 | 
				
			||||||
        // TODO: Maybe the inbox message for the sent invite should be deleted?
 | 
					            // 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 });
 | 
					        await GuildMember.deleteOne({ _id: guildMember._id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -56,6 +73,7 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        await guild.save();
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        _id: payload.userId,
 | 
					        _id: payload.userId,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										21
									
								
								src/controllers/api/removeIgnoredUserController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/controllers/api/removeIgnoredUserController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { Account, Ignore } from "@/src/models/loginModel";
 | 
				
			||||||
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removeIgnoredUserController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    const data = getJSONfromString<IRemoveIgnoredUserRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const ignoreeAccount = await Account.findOne(
 | 
				
			||||||
 | 
					        { DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
 | 
				
			||||||
 | 
					        "_id"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (ignoreeAccount) {
 | 
				
			||||||
 | 
					        await Ignore.deleteOne({ ignorer: accountId, ignoree: ignoreeAccount._id });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IRemoveIgnoredUserRequest {
 | 
				
			||||||
 | 
					    playerName: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,6 +1,9 @@
 | 
				
			|||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -8,7 +11,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
 | 
					    const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
 | 
				
			||||||
    if ("YearIteration" in request) {
 | 
					    if ("YearIteration" in request) {
 | 
				
			||||||
        const inventory = await getInventory(accountId);
 | 
					        const inventory = await getInventory(accountId, "DialogueHistory");
 | 
				
			||||||
        if (inventory.DialogueHistory) {
 | 
					        if (inventory.DialogueHistory) {
 | 
				
			||||||
            inventory.DialogueHistory.YearIteration = request.YearIteration;
 | 
					            inventory.DialogueHistory.YearIteration = request.YearIteration;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
@ -21,33 +24,25 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        if (!inventory.DialogueHistory) {
 | 
					        if (!inventory.DialogueHistory) {
 | 
				
			||||||
            throw new Error("bad inventory state");
 | 
					            throw new Error("bad inventory state");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (request.QueuedDialogues.length != 0 || request.OtherDialogueInfos.length != 0) {
 | 
					        const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
            logger.error(`saveDialogue request not fully handled: ${String(req.body)}`);
 | 
					        const tomorrowAt0Utc = config.noKimCooldowns
 | 
				
			||||||
        }
 | 
					            ? Date.now()
 | 
				
			||||||
 | 
					            : (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
 | 
				
			||||||
        inventory.DialogueHistory.Dialogues ??= [];
 | 
					        inventory.DialogueHistory.Dialogues ??= [];
 | 
				
			||||||
        let dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == request.DialogueName);
 | 
					        const dialogue = getDialogue(inventory, request.DialogueName);
 | 
				
			||||||
        if (!dialogue) {
 | 
					 | 
				
			||||||
            dialogue =
 | 
					 | 
				
			||||||
                inventory.DialogueHistory.Dialogues[
 | 
					 | 
				
			||||||
                    inventory.DialogueHistory.Dialogues.push({
 | 
					 | 
				
			||||||
                        Rank: 0,
 | 
					 | 
				
			||||||
                        Chemistry: 0,
 | 
					 | 
				
			||||||
                        AvailableDate: new Date(0),
 | 
					 | 
				
			||||||
                        AvailableGiftDate: new Date(0),
 | 
					 | 
				
			||||||
                        RankUpExpiry: new Date(0),
 | 
					 | 
				
			||||||
                        BountyChemExpiry: new Date(0),
 | 
					 | 
				
			||||||
                        Gifts: [],
 | 
					 | 
				
			||||||
                        Booleans: [],
 | 
					 | 
				
			||||||
                        Completed: [],
 | 
					 | 
				
			||||||
                        DialogueName: request.DialogueName
 | 
					 | 
				
			||||||
                    }) - 1
 | 
					 | 
				
			||||||
                ];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        dialogue.Rank = request.Rank;
 | 
					        dialogue.Rank = request.Rank;
 | 
				
			||||||
        dialogue.Chemistry = request.Chemistry;
 | 
					        dialogue.Chemistry = request.Chemistry;
 | 
				
			||||||
        //dialogue.QueuedDialogues = request.QueuedDialogues;
 | 
					        if (request.Data) {
 | 
				
			||||||
 | 
					            dialogue.QueuedDialogues = request.QueuedDialogues;
 | 
				
			||||||
            for (const bool of request.Booleans) {
 | 
					            for (const bool of request.Booleans) {
 | 
				
			||||||
                dialogue.Booleans.push(bool);
 | 
					                dialogue.Booleans.push(bool);
 | 
				
			||||||
 | 
					                if (bool == "LizzieShawzin") {
 | 
				
			||||||
 | 
					                    await addEmailItem(
 | 
				
			||||||
 | 
					                        inventory,
 | 
				
			||||||
 | 
					                        "/Lotus/Types/Items/EmailItems/LizzieShawzinSkinEmailItem",
 | 
				
			||||||
 | 
					                        inventoryChanges
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            for (const bool of request.ResetBooleans) {
 | 
					            for (const bool of request.ResetBooleans) {
 | 
				
			||||||
                const index = dialogue.Booleans.findIndex(x => x == bool);
 | 
					                const index = dialogue.Booleans.findIndex(x => x == bool);
 | 
				
			||||||
@ -56,13 +51,36 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            dialogue.Completed.push(request.Data);
 | 
					            dialogue.Completed.push(request.Data);
 | 
				
			||||||
        const tomorrowAt0Utc = (Math.trunc(Date.now() / (86400 * 1000)) + 1) * 86400 * 1000;
 | 
					 | 
				
			||||||
            dialogue.AvailableDate = new Date(tomorrowAt0Utc);
 | 
					            dialogue.AvailableDate = new Date(tomorrowAt0Utc);
 | 
				
			||||||
 | 
					            for (const info of request.OtherDialogueInfos) {
 | 
				
			||||||
 | 
					                const otherDialogue = getDialogue(inventory, info.Dialogue);
 | 
				
			||||||
 | 
					                if (info.Tag != "") {
 | 
				
			||||||
 | 
					                    otherDialogue.QueuedDialogues.push(info.Tag);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                otherDialogue.Chemistry += info.Value; // unsure
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json({
 | 
					            res.json({
 | 
				
			||||||
            InventoryChanges: [],
 | 
					                InventoryChanges: inventoryChanges,
 | 
				
			||||||
                AvailableDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
 | 
					                AvailableDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					        } else if (request.Gift) {
 | 
				
			||||||
 | 
					            const inventoryChanges = updateCurrency(inventory, request.Gift.Cost, false);
 | 
				
			||||||
 | 
					            const gift = dialogue.Gifts.find(x => x.Item == request.Gift!.Item);
 | 
				
			||||||
 | 
					            if (gift) {
 | 
				
			||||||
 | 
					                gift.GiftedQuantity += 1;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                dialogue.Gifts.push({ Item: request.Gift.Item, GiftedQuantity: 1 });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            dialogue.AvailableGiftDate = new Date(tomorrowAt0Utc);
 | 
				
			||||||
 | 
					            await inventory.save();
 | 
				
			||||||
 | 
					            res.json({
 | 
				
			||||||
 | 
					                InventoryChanges: inventoryChanges,
 | 
				
			||||||
 | 
					                AvailableGiftDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            logger.error(`saveDialogue request not fully handled: ${String(req.body)}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,11 +95,17 @@ interface SaveCompletedDialogueRequest {
 | 
				
			|||||||
    Rank: number;
 | 
					    Rank: number;
 | 
				
			||||||
    Chemistry: number;
 | 
					    Chemistry: number;
 | 
				
			||||||
    CompletionType: number;
 | 
					    CompletionType: number;
 | 
				
			||||||
    QueuedDialogues: string[]; // unsure
 | 
					    QueuedDialogues: string[];
 | 
				
			||||||
 | 
					    Gift?: {
 | 
				
			||||||
 | 
					        Item: string;
 | 
				
			||||||
 | 
					        GainedChemistry: number;
 | 
				
			||||||
 | 
					        Cost: number;
 | 
				
			||||||
 | 
					        GiftedQuantity: number;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    Booleans: string[];
 | 
					    Booleans: string[];
 | 
				
			||||||
    ResetBooleans: string[];
 | 
					    ResetBooleans: string[];
 | 
				
			||||||
    Data: ICompletedDialogue;
 | 
					    Data?: ICompletedDialogue;
 | 
				
			||||||
    OtherDialogueInfos: IOtherDialogueInfo[]; // unsure
 | 
					    OtherDialogueInfos: IOtherDialogueInfo[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IOtherDialogueInfo {
 | 
					interface IOtherDialogueInfo {
 | 
				
			||||||
@ -89,3 +113,26 @@ interface IOtherDialogueInfo {
 | 
				
			|||||||
    Tag: string;
 | 
					    Tag: string;
 | 
				
			||||||
    Value: number;
 | 
					    Value: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
 | 
				
			||||||
 | 
					    let dialogue = inventory.DialogueHistory!.Dialogues!.find(x => x.DialogueName == dialogueName);
 | 
				
			||||||
 | 
					    if (!dialogue) {
 | 
				
			||||||
 | 
					        dialogue =
 | 
				
			||||||
 | 
					            inventory.DialogueHistory!.Dialogues![
 | 
				
			||||||
 | 
					                inventory.DialogueHistory!.Dialogues!.push({
 | 
				
			||||||
 | 
					                    Rank: 0,
 | 
				
			||||||
 | 
					                    Chemistry: 0,
 | 
				
			||||||
 | 
					                    AvailableDate: new Date(0),
 | 
				
			||||||
 | 
					                    AvailableGiftDate: new Date(0),
 | 
				
			||||||
 | 
					                    RankUpExpiry: new Date(0),
 | 
				
			||||||
 | 
					                    BountyChemExpiry: new Date(0),
 | 
				
			||||||
 | 
					                    QueuedDialogues: [],
 | 
				
			||||||
 | 
					                    Gifts: [],
 | 
				
			||||||
 | 
					                    Booleans: [],
 | 
				
			||||||
 | 
					                    Completed: [],
 | 
				
			||||||
 | 
					                    DialogueName: dialogueName
 | 
				
			||||||
 | 
					                }) - 1
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return dialogue;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,32 +0,0 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					 | 
				
			||||||
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
 | 
					 | 
				
			||||||
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
 | 
					 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const saveLoadoutController: RequestHandler = async (req, res) => {
 | 
					 | 
				
			||||||
    //validate here
 | 
					 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        const body: ISaveLoadoutRequest = JSON.parse(req.body as string) as ISaveLoadoutRequest;
 | 
					 | 
				
			||||||
        // console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					 | 
				
			||||||
        const { UpgradeVer, ...equipmentChanges } = body;
 | 
					 | 
				
			||||||
        const newLoadoutId = await handleInventoryItemConfigChange(equipmentChanges, accountId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //send back new loadout id, if new loadout was added
 | 
					 | 
				
			||||||
        if (newLoadoutId) {
 | 
					 | 
				
			||||||
            res.send(newLoadoutId);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        res.status(200).end();
 | 
					 | 
				
			||||||
    } catch (error: unknown) {
 | 
					 | 
				
			||||||
        if (error instanceof Error) {
 | 
					 | 
				
			||||||
            logger.error(`error in saveLoadoutController: ${error.message}`);
 | 
					 | 
				
			||||||
            res.status(400).json({ error: error.message });
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            res.status(400).json({ error: "unknown error" });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										21
									
								
								src/controllers/api/saveLoadoutController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/controllers/api/saveLoadoutController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
 | 
				
			||||||
 | 
					import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const saveLoadoutController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const body: ISaveLoadoutRequest = JSON.parse(req.body as string) as ISaveLoadoutRequest;
 | 
				
			||||||
 | 
					    // console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    const { UpgradeVer, ...equipmentChanges } = body;
 | 
				
			||||||
 | 
					    const newLoadoutId = await handleInventoryItemConfigChange(equipmentChanges, accountId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //send back new loadout id, if new loadout was added
 | 
				
			||||||
 | 
					    if (newLoadoutId) {
 | 
				
			||||||
 | 
					        res.send(newLoadoutId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -6,14 +6,64 @@ import {
 | 
				
			|||||||
    addRecipes,
 | 
					    addRecipes,
 | 
				
			||||||
    addMiscItems,
 | 
					    addMiscItems,
 | 
				
			||||||
    addConsumables,
 | 
					    addConsumables,
 | 
				
			||||||
    freeUpSlot
 | 
					    freeUpSlot,
 | 
				
			||||||
 | 
					    combineInventoryChanges,
 | 
				
			||||||
 | 
					    addCrewShipRawSalvage
 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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<keyof TInventoryDatabaseDocument>();
 | 
				
			||||||
 | 
					    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 as keyof TInventoryDatabaseDocument);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
 | 
				
			||||||
 | 
					        requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
 | 
				
			||||||
 | 
					        requiredFields.add("CrewShipRawSalvage");
 | 
				
			||||||
 | 
					        if (payload.Items.CrewShipWeapons) {
 | 
				
			||||||
 | 
					            requiredFields.add("CrewShipSalvagedWeapons");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (payload.Items.CrewShipWeaponSkins) {
 | 
				
			||||||
 | 
					            requiredFields.add("CrewShipSalvagedWeaponSkins");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Give currency
 | 
					    // Give currency
 | 
				
			||||||
    if (payload.SellCurrency == "SC_RegularCredits") {
 | 
					    if (payload.SellCurrency == "SC_RegularCredits") {
 | 
				
			||||||
@ -34,10 +84,14 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                ItemCount: payload.SellPrice
 | 
					                ItemCount: payload.SellPrice
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					    } else if (payload.SellCurrency == "SC_Resources") {
 | 
				
			||||||
 | 
					        // Will add appropriate MiscItems from CrewShipWeapons or CrewShipWeaponSkins
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
 | 
					        throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Remove item(s)
 | 
					    // Remove item(s)
 | 
				
			||||||
    if (payload.Items.Suits) {
 | 
					    if (payload.Items.Suits) {
 | 
				
			||||||
        payload.Items.Suits.forEach(sellItem => {
 | 
					        payload.Items.Suits.forEach(sellItem => {
 | 
				
			||||||
@ -110,6 +164,56 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            inventory.Drones.pull({ _id: sellItem.String });
 | 
					            inventory.Drones.pull({ _id: sellItem.String });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.CrewShipWeapons) {
 | 
				
			||||||
 | 
					        payload.Items.CrewShipWeapons.forEach(sellItem => {
 | 
				
			||||||
 | 
					            if (sellItem.String[0] == "/") {
 | 
				
			||||||
 | 
					                addCrewShipRawSalvage(inventory, [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        ItemType: sellItem.String,
 | 
				
			||||||
 | 
					                        ItemCount: sellItem.Count * -1
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                const index = inventory.CrewShipWeapons.findIndex(x => x._id.equals(sellItem.String));
 | 
				
			||||||
 | 
					                if (index != -1) {
 | 
				
			||||||
 | 
					                    if (payload.SellCurrency == "SC_Resources") {
 | 
				
			||||||
 | 
					                        refundPartialBuildCosts(inventory, inventory.CrewShipWeapons[index].ItemType, inventoryChanges);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    inventory.CrewShipWeapons.splice(index, 1);
 | 
				
			||||||
 | 
					                    freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    inventory.CrewShipSalvagedWeapons.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.CrewShipWeaponSkins) {
 | 
				
			||||||
 | 
					        payload.Items.CrewShipWeaponSkins.forEach(sellItem => {
 | 
				
			||||||
 | 
					            if (sellItem.String[0] == "/") {
 | 
				
			||||||
 | 
					                addCrewShipRawSalvage(inventory, [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        ItemType: sellItem.String,
 | 
				
			||||||
 | 
					                        ItemCount: sellItem.Count * -1
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                const index = inventory.CrewShipWeaponSkins.findIndex(x => x._id.equals(sellItem.String));
 | 
				
			||||||
 | 
					                if (index != -1) {
 | 
				
			||||||
 | 
					                    if (payload.SellCurrency == "SC_Resources") {
 | 
				
			||||||
 | 
					                        refundPartialBuildCosts(
 | 
				
			||||||
 | 
					                            inventory,
 | 
				
			||||||
 | 
					                            inventory.CrewShipWeaponSkins[index].ItemType,
 | 
				
			||||||
 | 
					                            inventoryChanges
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    inventory.CrewShipWeaponSkins.splice(index, 1);
 | 
				
			||||||
 | 
					                    freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    inventory.CrewShipSalvagedWeaponSkins.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (payload.Items.Consumables) {
 | 
					    if (payload.Items.Consumables) {
 | 
				
			||||||
        const consumablesChanges = [];
 | 
					        const consumablesChanges = [];
 | 
				
			||||||
        for (const sellItem of payload.Items.Consumables) {
 | 
					        for (const sellItem of payload.Items.Consumables) {
 | 
				
			||||||
@ -156,7 +260,9 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({});
 | 
					    res.json({
 | 
				
			||||||
 | 
					        inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ISellRequest {
 | 
					interface ISellRequest {
 | 
				
			||||||
@ -177,6 +283,8 @@ interface ISellRequest {
 | 
				
			|||||||
        OperatorAmps?: ISellItem[];
 | 
					        OperatorAmps?: ISellItem[];
 | 
				
			||||||
        Hoverboards?: ISellItem[];
 | 
					        Hoverboards?: ISellItem[];
 | 
				
			||||||
        Drones?: ISellItem[];
 | 
					        Drones?: ISellItem[];
 | 
				
			||||||
 | 
					        CrewShipWeapons?: ISellItem[];
 | 
				
			||||||
 | 
					        CrewShipWeaponSkins?: ISellItem[];
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    SellPrice: number;
 | 
					    SellPrice: number;
 | 
				
			||||||
    SellCurrency:
 | 
					    SellCurrency:
 | 
				
			||||||
@ -193,3 +301,33 @@ interface ISellItem {
 | 
				
			|||||||
    String: string; // oid or uniqueName
 | 
					    String: string; // oid or uniqueName
 | 
				
			||||||
    Count: number;
 | 
					    Count: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const refundPartialBuildCosts = (
 | 
				
			||||||
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
 | 
					    itemType: string,
 | 
				
			||||||
 | 
					    inventoryChanges: IInventoryChanges
 | 
				
			||||||
 | 
					): void => {
 | 
				
			||||||
 | 
					    // House versions
 | 
				
			||||||
 | 
					    const research = Object.values(ExportDojoRecipes.research).find(x => x.resultType == itemType);
 | 
				
			||||||
 | 
					    if (research) {
 | 
				
			||||||
 | 
					        const miscItemChanges = research.ingredients.map(x => ({
 | 
				
			||||||
 | 
					            ItemType: x.ItemType,
 | 
				
			||||||
 | 
					            ItemCount: Math.trunc(x.ItemCount * 0.8)
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					        addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
 | 
					        combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Sigma versions
 | 
				
			||||||
 | 
					    const recipe = Object.values(ExportDojoRecipes.fabrications).find(x => x.resultType == itemType);
 | 
				
			||||||
 | 
					    if (recipe) {
 | 
				
			||||||
 | 
					        const miscItemChanges = recipe.ingredients.map(x => ({
 | 
				
			||||||
 | 
					            ItemType: x.ItemType,
 | 
				
			||||||
 | 
					            ItemCount: Math.trunc(x.ItemCount * 0.8)
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					        addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
 | 
					        combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										31
									
								
								src/controllers/api/sendMsgToInBoxController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/controllers/api/sendMsgToInBoxController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { createMessage } from "@/src/services/inboxService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sendMsgToInBoxController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const data = getJSONfromString<ISendMsgToInBoxRequest>(String(req.body));
 | 
				
			||||||
 | 
					    await createMessage(accountId, [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            sub: data.title,
 | 
				
			||||||
 | 
					            msg: data.message,
 | 
				
			||||||
 | 
					            sndr: data.sender ?? "/Lotus/Language/Bosses/Ordis",
 | 
				
			||||||
 | 
					            icon: data.senderIcon,
 | 
				
			||||||
 | 
					            highPriority: data.highPriority,
 | 
				
			||||||
 | 
					            transmission: data.transmission,
 | 
				
			||||||
 | 
					            att: data.attachments
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISendMsgToInBoxRequest {
 | 
				
			||||||
 | 
					    title: string;
 | 
				
			||||||
 | 
					    message: string;
 | 
				
			||||||
 | 
					    sender?: string;
 | 
				
			||||||
 | 
					    senderIcon?: string;
 | 
				
			||||||
 | 
					    highPriority?: boolean;
 | 
				
			||||||
 | 
					    transmission?: string;
 | 
				
			||||||
 | 
					    attachments?: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										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();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										34
									
								
								src/controllers/api/setDojoComponentColorsController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/controllers/api/setDojoComponentColorsController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					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 setDojoComponentColorsController: 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 data = getJSONfromString<ISetDojoComponentColorsRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const component = guild.DojoComponents.id(data.ComponentId)!;
 | 
				
			||||||
 | 
					    //const deco = component.Decos!.find(x => x._id.equals(data.DecoId))!;
 | 
				
			||||||
 | 
					    //deco.Pending = true;
 | 
				
			||||||
 | 
					    //component.PaintBot = new Types.ObjectId(data.DecoId);
 | 
				
			||||||
 | 
					    if ("lights" in req.query) {
 | 
				
			||||||
 | 
					        component.PendingLights = data.Colours;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        component.PendingColors = data.Colours;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.json(await getDojoClient(guild, 0, component._id));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISetDojoComponentColorsRequest {
 | 
				
			||||||
 | 
					    ComponentId: string;
 | 
				
			||||||
 | 
					    DecoId: string;
 | 
				
			||||||
 | 
					    Colours: number[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								src/controllers/api/setDojoComponentSettingsController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/controllers/api/setDojoComponentSettingsController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					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 setDojoComponentSettingsController: 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 component = guild.DojoComponents.id(req.query.componentId)!;
 | 
				
			||||||
 | 
					    const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
 | 
				
			||||||
 | 
					    component.Settings = data.Settings;
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.json(await getDojoClient(guild, 0, component._id));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISetDojoComponentSettingsRequest {
 | 
				
			||||||
 | 
					    Settings: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,21 +1,44 @@
 | 
				
			|||||||
import { Guild } from "@/src/models/guildModel";
 | 
					import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
import { hasGuildPermission } from "@/src/services/guildService";
 | 
					import { hasGuildPermissionEx } from "@/src/services/guildService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
					import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
import { GuildPermission } from "@/src/types/guildTypes";
 | 
					import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setGuildMotdController: RequestHandler = async (req, res) => {
 | 
					export const setGuildMotdController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const account = await getAccountForRequest(req);
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(account._id.toString(), "GuildId");
 | 
					    const inventory = await getInventory(account._id.toString(), "GuildId");
 | 
				
			||||||
    const guild = (await Guild.findById(inventory.GuildId!))!;
 | 
					    const guild = (await Guild.findById(inventory.GuildId!))!;
 | 
				
			||||||
    if (!(await hasGuildPermission(guild, account._id, GuildPermission.Herald))) {
 | 
					    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");
 | 
					            res.status(400).json("Invalid permission");
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const IsLongMOTD = "longMOTD" in req.query;
 | 
					        const alliance = (await Alliance.findById(guild.AllianceId!))!;
 | 
				
			||||||
    const MOTD = req.body ? String(req.body) : undefined;
 | 
					        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 (IsLongMOTD) {
 | 
				
			||||||
            if (MOTD) {
 | 
					            if (MOTD) {
 | 
				
			||||||
@ -30,6 +53,7 @@ export const setGuildMotdController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            guild.MOTD = MOTD ?? "";
 | 
					            guild.MOTD = MOTD ?? "";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        await guild.save();
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({ IsLongMOTD, MOTD });
 | 
					    res.json({ IsLongMOTD, MOTD });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
 | 
					import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
 | 
					import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -7,5 +7,17 @@ export const setPlacedDecoInfoController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest;
 | 
					    const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest;
 | 
				
			||||||
    await handleSetPlacedDecoInfo(accountId, payload);
 | 
					    await handleSetPlacedDecoInfo(accountId, payload);
 | 
				
			||||||
    res.end();
 | 
					    res.json({
 | 
				
			||||||
 | 
					        DecoId: payload.DecoId,
 | 
				
			||||||
 | 
					        IsPicture: true,
 | 
				
			||||||
 | 
					        PictureFrameInfo: payload.PictureFrameInfo,
 | 
				
			||||||
 | 
					        BootLocation: payload.BootLocation
 | 
				
			||||||
 | 
					    } satisfies ISetPlacedDecoInfoResponse);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISetPlacedDecoInfoResponse {
 | 
				
			||||||
 | 
					    DecoId: string;
 | 
				
			||||||
 | 
					    IsPicture: boolean;
 | 
				
			||||||
 | 
					    PictureFrameInfo?: IPictureFrameInfo;
 | 
				
			||||||
 | 
					    BootLocation?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,29 +3,40 @@ import { RequestHandler } from "express";
 | 
				
			|||||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
					import { getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
				
			||||||
import { IOid } from "@/src/types/commonTypes";
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/shipTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
 | 
					export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const personalRooms = await getPersonalRooms(accountId);
 | 
					    const personalRooms = await getPersonalRooms(accountId);
 | 
				
			||||||
    const body = JSON.parse(String(req.body)) as ISetShipFavouriteLoadoutRequest;
 | 
					    const body = JSON.parse(String(req.body)) as ISetShipFavouriteLoadoutRequest;
 | 
				
			||||||
    if (body.BootLocation != "SHOP") {
 | 
					    if (body.BootLocation == "LISET") {
 | 
				
			||||||
        throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
 | 
					        personalRooms.Ship.FavouriteLoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
 | 
				
			||||||
    }
 | 
					    } else if (body.BootLocation == "APARTMENT") {
 | 
				
			||||||
    const display = personalRooms.TailorShop.FavouriteLoadouts.find(x => x.Tag == body.TagName);
 | 
					        updateTaggedDisplay(personalRooms.Apartment.FavouriteLoadouts, body);
 | 
				
			||||||
    if (display) {
 | 
					    } else if (body.BootLocation == "SHOP") {
 | 
				
			||||||
        display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
 | 
					        updateTaggedDisplay(personalRooms.TailorShop.FavouriteLoadouts, body);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        personalRooms.TailorShop.FavouriteLoadouts.push({
 | 
					        console.log(body);
 | 
				
			||||||
            Tag: body.TagName,
 | 
					        throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
 | 
				
			||||||
            LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await personalRooms.save();
 | 
					    await personalRooms.save();
 | 
				
			||||||
    res.json({});
 | 
					    res.json({});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ISetShipFavouriteLoadoutRequest {
 | 
					interface ISetShipFavouriteLoadoutRequest {
 | 
				
			||||||
    BootLocation: string;
 | 
					    BootLocation: TBootLocation;
 | 
				
			||||||
    FavouriteLoadoutId: IOid;
 | 
					    FavouriteLoadoutId: IOid;
 | 
				
			||||||
    TagName: string;
 | 
					    TagName?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateTaggedDisplay = (arr: IFavouriteLoadoutDatabase[], body: ISetShipFavouriteLoadoutRequest): void => {
 | 
				
			||||||
 | 
					    const display = arr.find(x => x.Tag == body.TagName!);
 | 
				
			||||||
 | 
					    if (display) {
 | 
				
			||||||
 | 
					        display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        arr.push({
 | 
				
			||||||
 | 
					            Tag: body.TagName!,
 | 
				
			||||||
 | 
					            LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										48
									
								
								src/controllers/api/setShipVignetteController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/controllers/api/setShipVignetteController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					import { addMiscItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setShipVignetteController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "MiscItems");
 | 
				
			||||||
 | 
					    const personalRooms = await getPersonalRooms(accountId);
 | 
				
			||||||
 | 
					    const body = JSON.parse(String(req.body)) as ISetShipVignetteRequest;
 | 
				
			||||||
 | 
					    personalRooms.Ship.Wallpaper = body.Wallpaper;
 | 
				
			||||||
 | 
					    personalRooms.Ship.Vignette = body.Vignette;
 | 
				
			||||||
 | 
					    personalRooms.Ship.VignetteFish ??= [];
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					    for (let i = 0; i != body.Fish.length; ++i) {
 | 
				
			||||||
 | 
					        if (body.Fish[i] && !personalRooms.Ship.VignetteFish[i]) {
 | 
				
			||||||
 | 
					            logger.debug(`moving ${body.Fish[i]} from inventory to vignette slot ${i}`);
 | 
				
			||||||
 | 
					            const miscItemsDelta = [{ ItemType: body.Fish[i], ItemCount: -1 }];
 | 
				
			||||||
 | 
					            addMiscItems(inventory, miscItemsDelta);
 | 
				
			||||||
 | 
					            combineInventoryChanges(inventoryChanges, { MiscItems: miscItemsDelta });
 | 
				
			||||||
 | 
					        } else if (personalRooms.Ship.VignetteFish[i] && !body.Fish[i]) {
 | 
				
			||||||
 | 
					            logger.debug(`moving ${personalRooms.Ship.VignetteFish[i]} from vignette slot ${i} to inventory`);
 | 
				
			||||||
 | 
					            const miscItemsDelta = [{ ItemType: personalRooms.Ship.VignetteFish[i], ItemCount: +1 }];
 | 
				
			||||||
 | 
					            addMiscItems(inventory, miscItemsDelta);
 | 
				
			||||||
 | 
					            combineInventoryChanges(inventoryChanges, { MiscItems: miscItemsDelta });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    personalRooms.Ship.VignetteFish = body.Fish;
 | 
				
			||||||
 | 
					    if (body.VignetteDecos.length) {
 | 
				
			||||||
 | 
					        logger.error(`setShipVignette request not fully handled:`, body);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await Promise.all([inventory.save(), personalRooms.save()]);
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        Wallpaper: body.Wallpaper,
 | 
				
			||||||
 | 
					        Vignette: body.Vignette,
 | 
				
			||||||
 | 
					        VignetteFish: body.Fish,
 | 
				
			||||||
 | 
					        InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISetShipVignetteRequest {
 | 
				
			||||||
 | 
					    Wallpaper: string;
 | 
				
			||||||
 | 
					    Vignette: string;
 | 
				
			||||||
 | 
					    Fish: string[];
 | 
				
			||||||
 | 
					    VignetteDecos: unknown[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -90,7 +90,6 @@ export const startRecipeController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                spectreLoadout.LongGuns = item.ItemType;
 | 
					                spectreLoadout.LongGuns = item.ItemType;
 | 
				
			||||||
                spectreLoadout.LongGunsModularParts = item.ModularParts;
 | 
					                spectreLoadout.LongGunsModularParts = item.ModularParts;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
					 | 
				
			||||||
                console.assert(type == "/Lotus/Types/Game/LotusMeleeWeapon");
 | 
					                console.assert(type == "/Lotus/Types/Game/LotusMeleeWeapon");
 | 
				
			||||||
                const item = inventory.Melee.id(oid)!;
 | 
					                const item = inventory.Melee.id(oid)!;
 | 
				
			||||||
                spectreLoadout.Melee = item.ItemType;
 | 
					                spectreLoadout.Melee = item.ItemType;
 | 
				
			||||||
@ -111,6 +110,8 @@ export const startRecipeController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            inventory.PendingSpectreLoadouts.push(spectreLoadout);
 | 
					            inventory.PendingSpectreLoadouts.push(spectreLoadout);
 | 
				
			||||||
            logger.debug("pending spectre loadout", spectreLoadout);
 | 
					            logger.debug("pending spectre loadout", spectreLoadout);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    } else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
 | 
				
			||||||
 | 
					        pr.SuitToUnbrand = new Types.ObjectId(startRecipeRequest.Ids[recipe.ingredients.length + 0]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
				
			|||||||
@ -3,15 +3,9 @@ import { RequestHandler } from "express";
 | 
				
			|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
 | 
					import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
 | 
				
			||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
					import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
import {
 | 
					import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
    addItem,
 | 
					 | 
				
			||||||
    addMiscItems,
 | 
					 | 
				
			||||||
    combineInventoryChanges,
 | 
					 | 
				
			||||||
    getInventory,
 | 
					 | 
				
			||||||
    updateCurrency
 | 
					 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
 | 
					import { isStoreItem, toStoreItem } from "@/src/services/itemDataService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
 | 
					export const syndicateSacrificeController: RequestHandler = async (request, response) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(request);
 | 
					    const accountId = await getAccountIdForRequest(request);
 | 
				
			||||||
@ -57,7 +51,7 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
 | 
				
			|||||||
    syndicate.Title ??= 0;
 | 
					    syndicate.Title ??= 0;
 | 
				
			||||||
    syndicate.Title += 1;
 | 
					    syndicate.Title += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (syndicate.Title > 0 && manifest.favours.length != 0) {
 | 
					    if (syndicate.Title > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == syndicate.Title)) {
 | 
				
			||||||
        syndicate.FreeFavorsEarned ??= [];
 | 
					        syndicate.FreeFavorsEarned ??= [];
 | 
				
			||||||
        if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
 | 
					        if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
 | 
				
			||||||
            syndicate.FreeFavorsEarned.push(syndicate.Title);
 | 
					            syndicate.FreeFavorsEarned.push(syndicate.Title);
 | 
				
			||||||
@ -77,10 +71,13 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
 | 
				
			|||||||
            res.NewEpisodeReward = true;
 | 
					            res.NewEpisodeReward = true;
 | 
				
			||||||
            const reward = ExportNightwave.rewards[index];
 | 
					            const reward = ExportNightwave.rewards[index];
 | 
				
			||||||
            let rewardType = reward.uniqueName;
 | 
					            let rewardType = reward.uniqueName;
 | 
				
			||||||
            if (isStoreItem(rewardType)) {
 | 
					            if (!isStoreItem(rewardType)) {
 | 
				
			||||||
                rewardType = fromStoreItem(rewardType);
 | 
					                rewardType = toStoreItem(rewardType);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            combineInventoryChanges(res.InventoryChanges, await addItem(inventory, rewardType, reward.itemCount ?? 1));
 | 
					            combineInventoryChanges(
 | 
				
			||||||
 | 
					                res.InventoryChanges,
 | 
				
			||||||
 | 
					                (await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount)).InventoryChanges
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,9 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import {
 | 
					import { addMiscItems, addStanding, freeUpSlot, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
    addMiscItems,
 | 
					 | 
				
			||||||
    freeUpSlot,
 | 
					 | 
				
			||||||
    getInventory,
 | 
					 | 
				
			||||||
    getStandingLimit,
 | 
					 | 
				
			||||||
    updateStandingLimit
 | 
					 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					 | 
				
			||||||
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IOid } from "@/src/types/commonTypes";
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
 | 
					import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
 | 
				
			||||||
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
 | 
					 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
@ -61,38 +54,13 @@ export const syndicateStandingBonusController: RequestHandler = async (req, res)
 | 
				
			|||||||
        inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
 | 
					        inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let syndicate = inventory.Affiliations.find(x => x.Tag == request.Operation.AffiliationTag);
 | 
					    const affiliationMod = addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, true);
 | 
				
			||||||
    if (!syndicate) {
 | 
					 | 
				
			||||||
        syndicate =
 | 
					 | 
				
			||||||
            inventory.Affiliations[
 | 
					 | 
				
			||||||
                inventory.Affiliations.push({ Tag: request.Operation.AffiliationTag, Standing: 0 }) - 1
 | 
					 | 
				
			||||||
            ];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
 | 
					 | 
				
			||||||
    if (syndicate.Standing + gainedStanding > max) {
 | 
					 | 
				
			||||||
        gainedStanding = max - syndicate.Standing;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (syndicateMeta.medallionsCappedByDailyLimit) {
 | 
					 | 
				
			||||||
        if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
 | 
					 | 
				
			||||||
            gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    syndicate.Standing += gainedStanding;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        InventoryChanges: inventoryChanges,
 | 
					        InventoryChanges: inventoryChanges,
 | 
				
			||||||
        AffiliationMods: [
 | 
					        AffiliationMods: [affiliationMod]
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Tag: request.Operation.AffiliationTag,
 | 
					 | 
				
			||||||
                Standing: gainedStanding
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import { RequestHandler } from "express";
 | 
				
			|||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
					import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { createMessage } from "@/src/services/inboxService";
 | 
					import { createMessage } from "@/src/services/inboxService";
 | 
				
			||||||
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ITrainingResultsRequest {
 | 
					interface ITrainingResultsRequest {
 | 
				
			||||||
    numLevelsGained: number;
 | 
					    numLevelsGained: number;
 | 
				
			||||||
@ -22,11 +23,17 @@ const trainingResultController: RequestHandler = async (req, res): Promise<void>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const trainingResults = getJSONfromString<ITrainingResultsRequest>(String(req.body));
 | 
					    const trainingResults = getJSONfromString<ITrainingResultsRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId, "TrainingDate PlayerLevel TradesRemaining");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (trainingResults.numLevelsGained == 1) {
 | 
					    if (trainingResults.numLevelsGained == 1) {
 | 
				
			||||||
        inventory.TrainingDate = new Date(Date.now() + unixTimesInMs.hour * 23);
 | 
					        let time = Date.now();
 | 
				
			||||||
 | 
					        if (!config.noMasteryRankUpCooldown) {
 | 
				
			||||||
 | 
					            time += unixTimesInMs.hour * 23;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        inventory.TrainingDate = new Date(time);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        inventory.PlayerLevel += 1;
 | 
					        inventory.PlayerLevel += 1;
 | 
				
			||||||
 | 
					        inventory.TradesRemaining += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await createMessage(accountId, [
 | 
					        await createMessage(accountId, [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ import { updateShipFeature } from "@/src/services/personalRoomsService";
 | 
				
			|||||||
import { IUnlockShipFeatureRequest } from "@/src/types/requestTypes";
 | 
					import { IUnlockShipFeatureRequest } from "@/src/types/requestTypes";
 | 
				
			||||||
import { parseString } from "@/src/helpers/general";
 | 
					import { parseString } from "@/src/helpers/general";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
					 | 
				
			||||||
export const unlockShipFeatureController: RequestHandler = async (req, res) => {
 | 
					export const unlockShipFeatureController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = parseString(req.query.accountId);
 | 
					    const accountId = parseString(req.query.accountId);
 | 
				
			||||||
    const shipFeatureRequest = JSON.parse((req.body as string).toString()) as IUnlockShipFeatureRequest;
 | 
					    const shipFeatureRequest = JSON.parse((req.body as string).toString()) as IUnlockShipFeatureRequest;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,8 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
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 { addChallenges, addSeasonalChallengeHistory, getInventory } from "@/src/services/inventoryService";
 | 
					import { addChallenges, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { ExportNightwave } from "warframe-public-export-plus";
 | 
					 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					 | 
				
			||||||
import { IAffiliationMods } from "@/src/types/purchaseTypes";
 | 
					import { IAffiliationMods } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
 | 
					export const updateChallengeProgressController: RequestHandler = async (req, res) => {
 | 
				
			||||||
@ -12,41 +10,19 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
 | 
				
			|||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId, "ChallengeProgress SeasonChallengeHistory Affiliations");
 | 
					    const inventory = await getInventory(accountId, "ChallengeProgress SeasonChallengeHistory Affiliations");
 | 
				
			||||||
 | 
					    let affiliationMods: IAffiliationMods[] = [];
 | 
				
			||||||
    if (challenges.ChallengeProgress) {
 | 
					    if (challenges.ChallengeProgress) {
 | 
				
			||||||
        addChallenges(inventory, challenges.ChallengeProgress);
 | 
					        affiliationMods = addChallenges(inventory, challenges.ChallengeProgress, challenges.SeasonChallengeCompletions);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (challenges.SeasonChallengeHistory) {
 | 
					    if (challenges.SeasonChallengeHistory) {
 | 
				
			||||||
        addSeasonalChallengeHistory(inventory, challenges.SeasonChallengeHistory);
 | 
					        challenges.SeasonChallengeHistory.forEach(({ challenge, id }) => {
 | 
				
			||||||
    }
 | 
					            const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
 | 
				
			||||||
    const affiliationMods: IAffiliationMods[] = [];
 | 
					            if (itemIndex !== -1) {
 | 
				
			||||||
    if (challenges.ChallengeProgress && challenges.SeasonChallengeCompletions) {
 | 
					                inventory.SeasonChallengeHistory[itemIndex].id = id;
 | 
				
			||||||
        for (const challenge of challenges.SeasonChallengeCompletions) {
 | 
					            } else {
 | 
				
			||||||
            // Ignore challenges that weren't completed just now
 | 
					                inventory.SeasonChallengeHistory.push({ challenge, id });
 | 
				
			||||||
            if (!challenges.ChallengeProgress.find(x => challenge.challenge.indexOf(x.Name) != -1)) {
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const meta = ExportNightwave.challenges[challenge.challenge];
 | 
					 | 
				
			||||||
            logger.debug("Completed challenge", meta);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let affiliation = inventory.Affiliations.find(x => x.Tag == ExportNightwave.affiliationTag);
 | 
					 | 
				
			||||||
            if (!affiliation) {
 | 
					 | 
				
			||||||
                affiliation =
 | 
					 | 
				
			||||||
                    inventory.Affiliations[
 | 
					 | 
				
			||||||
                        inventory.Affiliations.push({
 | 
					 | 
				
			||||||
                            Tag: ExportNightwave.affiliationTag,
 | 
					 | 
				
			||||||
                            Standing: 0
 | 
					 | 
				
			||||||
                        }) - 1
 | 
					 | 
				
			||||||
                    ];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            affiliation.Standing += meta.standing;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (affiliationMods.length == 0) {
 | 
					 | 
				
			||||||
                affiliationMods.push({ Tag: ExportNightwave.affiliationTag });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            affiliationMods[0].Standing ??= 0;
 | 
					 | 
				
			||||||
            affiliationMods[0].Standing += meta.standing;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,6 @@ import { updateQuestKey, IUpdateQuestRequest } from "@/src/services/questService
 | 
				
			|||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
					 | 
				
			||||||
export const updateQuestController: RequestHandler = async (req, res) => {
 | 
					export const updateQuestController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = parseString(req.query.accountId);
 | 
					    const accountId = parseString(req.query.accountId);
 | 
				
			||||||
    const updateQuestRequest = getJSONfromString<IUpdateQuestRequest>((req.body as string).toString());
 | 
					    const updateQuestRequest = getJSONfromString<IUpdateQuestRequest>((req.body as string).toString());
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			|||||||
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getRecipeByResult } from "@/src/services/itemDataService";
 | 
					import { getRecipeByResult } from "@/src/services/itemDataService";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { addInfestedFoundryXP } from "./infestedFoundryController";
 | 
					import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const upgradesController: RequestHandler = async (req, res) => {
 | 
					export const upgradesController: RequestHandler = async (req, res) => {
 | 
				
			||||||
@ -25,7 +25,13 @@ export const upgradesController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
 | 
					            operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            updateCurrency(inventory, 10, true);
 | 
					            updateCurrency(inventory, 10, true);
 | 
				
			||||||
        } else {
 | 
					        } else if (
 | 
				
			||||||
 | 
					            operation.OperationType != "UOT_SWAP_POLARITY" &&
 | 
				
			||||||
 | 
					            operation.OperationType != "UOT_ABILITY_OVERRIDE"
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            if (!operation.UpgradeRequirement) {
 | 
				
			||||||
 | 
					                throw new Error(`${operation.OperationType} operation should be free?`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            addMiscItems(inventory, [
 | 
					            addMiscItems(inventory, [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    ItemType: operation.UpgradeRequirement,
 | 
					                    ItemType: operation.UpgradeRequirement,
 | 
				
			||||||
@ -66,6 +72,7 @@ export const upgradesController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            inventoryChanges.Recipes = recipeChanges;
 | 
					            inventoryChanges.Recipes = recipeChanges;
 | 
				
			||||||
            inventoryChanges.InfestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry;
 | 
					            inventoryChanges.InfestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry;
 | 
				
			||||||
 | 
					            applyCheatsToInfestedFoundry(inventoryChanges.InfestedFoundry!);
 | 
				
			||||||
        } else
 | 
					        } else
 | 
				
			||||||
            switch (operation.UpgradeRequirement) {
 | 
					            switch (operation.UpgradeRequirement) {
 | 
				
			||||||
                case "/Lotus/Types/Items/MiscItems/OrokinReactor":
 | 
					                case "/Lotus/Types/Items/MiscItems/OrokinReactor":
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										98
									
								
								src/controllers/custom/addModularEquipmentController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/controllers/custom/addModularEquipmentController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    getInventory,
 | 
				
			||||||
 | 
					    addEquipment,
 | 
				
			||||||
 | 
					    occupySlot,
 | 
				
			||||||
 | 
					    productCategoryToInventoryBin,
 | 
				
			||||||
 | 
					    applyDefaultUpgrades
 | 
				
			||||||
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
				
			||||||
 | 
					import { getDefaultUpgrades } from "@/src/services/itemDataService";
 | 
				
			||||||
 | 
					import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
 | 
					import { ExportWeapons } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addModularEquipmentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const requiredFields = new Set();
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const request = req.body as IAddModularEquipmentRequest;
 | 
				
			||||||
 | 
					    const category = modularWeaponTypes[request.ItemType];
 | 
				
			||||||
 | 
					    const inventoryBin = productCategoryToInventoryBin(category)!;
 | 
				
			||||||
 | 
					    requiredFields.add(category);
 | 
				
			||||||
 | 
					    requiredFields.add(inventoryBin);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    request.ModularParts.forEach(part => {
 | 
				
			||||||
 | 
					        if (ExportWeapons[part].gunType) {
 | 
				
			||||||
 | 
					            if (category == "LongGuns") {
 | 
				
			||||||
 | 
					                request.ItemType = {
 | 
				
			||||||
 | 
					                    GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
 | 
				
			||||||
 | 
					                    GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
 | 
				
			||||||
 | 
					                    GT_BEAM: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
 | 
				
			||||||
 | 
					                }[ExportWeapons[part].gunType];
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                request.ItemType = {
 | 
				
			||||||
 | 
					                    GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
 | 
				
			||||||
 | 
					                    GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
 | 
				
			||||||
 | 
					                    GT_BEAM: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
 | 
				
			||||||
 | 
					                }[ExportWeapons[part].gunType];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (request.ItemType == "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit") {
 | 
				
			||||||
 | 
					            if (part.includes("ZanukaPetPartHead")) {
 | 
				
			||||||
 | 
					                request.ItemType = {
 | 
				
			||||||
 | 
					                    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
 | 
				
			||||||
 | 
					                        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
 | 
				
			||||||
 | 
					                    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
 | 
				
			||||||
 | 
					                        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
 | 
				
			||||||
 | 
					                    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
 | 
				
			||||||
 | 
					                        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
 | 
				
			||||||
 | 
					                }[part]!;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const defaultUpgrades = getDefaultUpgrades(request.ModularParts);
 | 
				
			||||||
 | 
					    if (defaultUpgrades) {
 | 
				
			||||||
 | 
					        requiredFields.add("RawUpgrades");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const defaultWeaponsMap: Record<string, string[]> = {
 | 
				
			||||||
 | 
					        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit": [
 | 
				
			||||||
 | 
					            "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetMeleeWeaponIP"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit": [
 | 
				
			||||||
 | 
					            "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetMeleeWeaponIS"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit": [
 | 
				
			||||||
 | 
					            "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetMeleeWeaponPS"
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const defaultWeapons = defaultWeaponsMap[request.ItemType] as string[] | undefined;
 | 
				
			||||||
 | 
					    if (defaultWeapons) {
 | 
				
			||||||
 | 
					        for (const defaultWeapon of defaultWeapons) {
 | 
				
			||||||
 | 
					            const category = ExportWeapons[defaultWeapon].productCategory;
 | 
				
			||||||
 | 
					            requiredFields.add(category);
 | 
				
			||||||
 | 
					            requiredFields.add(productCategoryToInventoryBin(category));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
 | 
				
			||||||
 | 
					    if (defaultWeapons) {
 | 
				
			||||||
 | 
					        for (const defaultWeapon of defaultWeapons) {
 | 
				
			||||||
 | 
					            const category = ExportWeapons[defaultWeapon].productCategory;
 | 
				
			||||||
 | 
					            addEquipment(inventory, category, defaultWeapon);
 | 
				
			||||||
 | 
					            occupySlot(inventory, productCategoryToInventoryBin(category)!, true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const defaultOverwrites: Partial<IEquipmentDatabase> = {
 | 
				
			||||||
 | 
					        Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    addEquipment(inventory, category, request.ItemType, request.ModularParts, undefined, defaultOverwrites);
 | 
				
			||||||
 | 
					    occupySlot(inventory, inventoryBin, true);
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IAddModularEquipmentRequest {
 | 
				
			||||||
 | 
					    ItemType: string;
 | 
				
			||||||
 | 
					    ModularParts: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { Account } from "@/src/models/loginModel";
 | 
					import { Account, Ignore } from "@/src/models/loginModel";
 | 
				
			||||||
import { Inbox } from "@/src/models/inboxModel";
 | 
					import { Inbox } from "@/src/models/inboxModel";
 | 
				
			||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
					import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
				
			||||||
@ -9,13 +9,22 @@ import { Ship } from "@/src/models/shipModel";
 | 
				
			|||||||
import { Stats } from "@/src/models/statsModel";
 | 
					import { Stats } from "@/src/models/statsModel";
 | 
				
			||||||
import { GuildMember } from "@/src/models/guildModel";
 | 
					import { GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
import { Leaderboard } from "@/src/models/leaderboardModel";
 | 
					import { Leaderboard } from "@/src/models/leaderboardModel";
 | 
				
			||||||
 | 
					import { deleteGuild } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const deleteAccountController: RequestHandler = async (req, res) => {
 | 
					export const deleteAccountController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    // TODO: Handle the account being the creator of a guild
 | 
					
 | 
				
			||||||
 | 
					    // If account is the founding warlord of a guild, delete that guild as well.
 | 
				
			||||||
 | 
					    const guildMember = await GuildMember.findOne({ accountId, rank: 0, status: 0 });
 | 
				
			||||||
 | 
					    if (guildMember) {
 | 
				
			||||||
 | 
					        await deleteGuild(guildMember.guildId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await Promise.all([
 | 
					    await Promise.all([
 | 
				
			||||||
        Account.deleteOne({ _id: accountId }),
 | 
					        Account.deleteOne({ _id: accountId }),
 | 
				
			||||||
        GuildMember.deleteMany({ accountId: accountId }),
 | 
					        GuildMember.deleteMany({ accountId: accountId }),
 | 
				
			||||||
 | 
					        Ignore.deleteMany({ ignorer: accountId }),
 | 
				
			||||||
 | 
					        Ignore.deleteMany({ ignoree: accountId }),
 | 
				
			||||||
        Inbox.deleteMany({ ownerId: accountId }),
 | 
					        Inbox.deleteMany({ ownerId: accountId }),
 | 
				
			||||||
        Inventory.deleteOne({ accountOwnerId: accountId }),
 | 
					        Inventory.deleteOne({ accountOwnerId: accountId }),
 | 
				
			||||||
        Leaderboard.deleteMany({ ownerId: accountId }),
 | 
					        Leaderboard.deleteMany({ ownerId: accountId }),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
 | 
					import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
 | 
				
			||||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
 | 
					import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -12,9 +12,19 @@ export const getAccountInfoController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    const guildMember = await GuildMember.findOne({ accountId: account._id, status: 0 }, "guildId rank");
 | 
					    const guildMember = await GuildMember.findOne({ accountId: account._id, status: 0 }, "guildId rank");
 | 
				
			||||||
    if (guildMember) {
 | 
					    if (guildMember) {
 | 
				
			||||||
        const guild = (await Guild.findById(guildMember.guildId, "Ranks"))!;
 | 
					        const guild = (await Guild.findById(guildMember.guildId, "Ranks AllianceId"))!;
 | 
				
			||||||
        info.GuildId = guildMember.guildId.toString();
 | 
					        info.GuildId = guildMember.guildId.toString();
 | 
				
			||||||
        info.GuildPermissions = guild.Ranks[guildMember.rank].Permissions;
 | 
					        info.GuildPermissions = guild.Ranks[guildMember.rank].Permissions;
 | 
				
			||||||
 | 
					        info.GuildRank = guildMember.rank;
 | 
				
			||||||
 | 
					        if (guild.AllianceId) {
 | 
				
			||||||
 | 
					            //const alliance = (await Alliance.findById(guild.AllianceId))!;
 | 
				
			||||||
 | 
					            const allianceMember = (await AllianceMember.findOne({
 | 
				
			||||||
 | 
					                allianceId: guild.AllianceId,
 | 
				
			||||||
 | 
					                guildId: guild._id
 | 
				
			||||||
 | 
					            }))!;
 | 
				
			||||||
 | 
					            info.AllianceId = guild.AllianceId.toString();
 | 
				
			||||||
 | 
					            info.AlliancePermissions = allianceMember.Permissions;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.json(info);
 | 
					    res.json(info);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -24,4 +34,7 @@ interface IAccountInfo {
 | 
				
			|||||||
    IsAdministrator?: boolean;
 | 
					    IsAdministrator?: boolean;
 | 
				
			||||||
    GuildId?: string;
 | 
					    GuildId?: string;
 | 
				
			||||||
    GuildPermissions?: number;
 | 
					    GuildPermissions?: number;
 | 
				
			||||||
 | 
					    GuildRank?: number;
 | 
				
			||||||
 | 
					    AllianceId?: string;
 | 
				
			||||||
 | 
					    AlliancePermissions?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,8 +3,10 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService"
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
    ExportArcanes,
 | 
					    ExportArcanes,
 | 
				
			||||||
    ExportAvionics,
 | 
					    ExportAvionics,
 | 
				
			||||||
 | 
					    ExportCustoms,
 | 
				
			||||||
    ExportDrones,
 | 
					    ExportDrones,
 | 
				
			||||||
    ExportGear,
 | 
					    ExportGear,
 | 
				
			||||||
 | 
					    ExportKeys,
 | 
				
			||||||
    ExportMisc,
 | 
					    ExportMisc,
 | 
				
			||||||
    ExportRailjackWeapons,
 | 
					    ExportRailjackWeapons,
 | 
				
			||||||
    ExportRecipes,
 | 
					    ExportRecipes,
 | 
				
			||||||
@ -25,6 +27,8 @@ interface ListedItem {
 | 
				
			|||||||
    fusionLimit?: number;
 | 
					    fusionLimit?: number;
 | 
				
			||||||
    exalted?: string[];
 | 
					    exalted?: string[];
 | 
				
			||||||
    badReason?: "starter" | "frivolous" | "notraw";
 | 
					    badReason?: "starter" | "frivolous" | "notraw";
 | 
				
			||||||
 | 
					    partType?: string;
 | 
				
			||||||
 | 
					    chainLength?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
					const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
				
			||||||
@ -50,6 +54,8 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
    res.MechSuits = [];
 | 
					    res.MechSuits = [];
 | 
				
			||||||
    res.miscitems = [];
 | 
					    res.miscitems = [];
 | 
				
			||||||
    res.Syndicates = [];
 | 
					    res.Syndicates = [];
 | 
				
			||||||
 | 
					    res.OperatorAmps = [];
 | 
				
			||||||
 | 
					    res.QuestKeys = [];
 | 
				
			||||||
    for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
 | 
					    for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
 | 
				
			||||||
        res[item.productCategory].push({
 | 
					        res[item.productCategory].push({
 | 
				
			||||||
            uniqueName,
 | 
					            uniqueName,
 | 
				
			||||||
@ -66,20 +72,11 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
 | 
					    for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
 | 
				
			||||||
        if (
 | 
					        if (item.partType) {
 | 
				
			||||||
            uniqueName.split("/")[4] == "OperatorAmplifiers" ||
 | 
					 | 
				
			||||||
            uniqueName.split("/")[5] == "SUModularSecondarySet1" ||
 | 
					 | 
				
			||||||
            uniqueName.split("/")[5] == "SUModularPrimarySet1" ||
 | 
					 | 
				
			||||||
            uniqueName.split("/")[5] == "InfKitGun" ||
 | 
					 | 
				
			||||||
            uniqueName.split("/")[5] == "HoverboardParts" ||
 | 
					 | 
				
			||||||
            uniqueName.split("/")[5] == "ModularMelee01" ||
 | 
					 | 
				
			||||||
            uniqueName.split("/")[5] == "ModularMelee02" ||
 | 
					 | 
				
			||||||
            uniqueName.split("/")[5] == "ModularMeleeInfested" ||
 | 
					 | 
				
			||||||
            uniqueName.split("/")[6] == "CreaturePetParts"
 | 
					 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
            res.ModularParts.push({
 | 
					            res.ModularParts.push({
 | 
				
			||||||
                uniqueName,
 | 
					                uniqueName,
 | 
				
			||||||
                name: getString(item.name, lang)
 | 
					                name: getString(item.name, lang),
 | 
				
			||||||
 | 
					                partType: item.partType
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
 | 
					            if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
 | 
				
			||||||
                res.miscitems.push({
 | 
					                res.miscitems.push({
 | 
				
			||||||
@ -94,7 +91,8 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
                item.productCategory == "Melee" ||
 | 
					                item.productCategory == "Melee" ||
 | 
				
			||||||
                item.productCategory == "SpaceGuns" ||
 | 
					                item.productCategory == "SpaceGuns" ||
 | 
				
			||||||
                item.productCategory == "SpaceMelee" ||
 | 
					                item.productCategory == "SpaceMelee" ||
 | 
				
			||||||
                item.productCategory == "SentinelWeapons"
 | 
					                item.productCategory == "SentinelWeapons" ||
 | 
				
			||||||
 | 
					                item.productCategory == "OperatorAmps"
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                res[item.productCategory].push({
 | 
					                res[item.productCategory].push({
 | 
				
			||||||
                    uniqueName,
 | 
					                    uniqueName,
 | 
				
			||||||
@ -121,6 +119,7 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
 | 
					            name &&
 | 
				
			||||||
            uniqueName.substr(0, 30) != "/Lotus/Types/Game/Projections/" &&
 | 
					            uniqueName.substr(0, 30) != "/Lotus/Types/Game/Projections/" &&
 | 
				
			||||||
            uniqueName != "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLanthornBundle"
 | 
					            uniqueName != "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLanthornBundle"
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
@ -152,9 +151,11 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
        if (!item.hidden) {
 | 
					        if (!item.hidden) {
 | 
				
			||||||
            const resultName = getItemName(item.resultType);
 | 
					            const resultName = getItemName(item.resultType);
 | 
				
			||||||
            if (resultName) {
 | 
					            if (resultName) {
 | 
				
			||||||
 | 
					                let itemName = getString(resultName, lang);
 | 
				
			||||||
 | 
					                if (item.num > 1) itemName = `${itemName} X ${item.num}`;
 | 
				
			||||||
                res.miscitems.push({
 | 
					                res.miscitems.push({
 | 
				
			||||||
                    uniqueName: uniqueName,
 | 
					                    uniqueName: uniqueName,
 | 
				
			||||||
                    name: recipeNameTemplate.replace("|ITEM|", getString(resultName, lang))
 | 
					                    name: recipeNameTemplate.replace("|ITEM|", itemName)
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -171,6 +172,12 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
            name: getString(item.name, lang)
 | 
					            name: getString(item.name, lang)
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
 | 
				
			||||||
 | 
					        res.miscitems.push({
 | 
				
			||||||
 | 
					            uniqueName: uniqueName,
 | 
				
			||||||
 | 
					            name: getString(item.name, lang)
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.mods = [];
 | 
					    res.mods = [];
 | 
				
			||||||
    for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
 | 
					    for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
 | 
				
			||||||
@ -213,6 +220,20 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
            name: getString(syndicate.name, lang)
 | 
					            name: getString(syndicate.name, lang)
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    for (const [uniqueName, key] of Object.entries(ExportKeys)) {
 | 
				
			||||||
 | 
					        if (key.chainStages) {
 | 
				
			||||||
 | 
					            res.QuestKeys.push({
 | 
				
			||||||
 | 
					                uniqueName,
 | 
				
			||||||
 | 
					                name: getString(key.name || "", lang),
 | 
				
			||||||
 | 
					                chainLength: key.chainStages.length
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else if (key.name) {
 | 
				
			||||||
 | 
					            res.miscitems.push({
 | 
				
			||||||
 | 
					                uniqueName,
 | 
				
			||||||
 | 
					                name: getString(key.name, lang)
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response.json({
 | 
					    response.json({
 | 
				
			||||||
        archonCrystalUpgrades,
 | 
					        archonCrystalUpgrades,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,11 @@
 | 
				
			|||||||
import { addString } from "@/src/controllers/api/inventoryController";
 | 
					 | 
				
			||||||
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 { addQuestKey, completeQuest, IUpdateQuestRequest, updateQuestKey } from "@/src/services/questService";
 | 
					import {
 | 
				
			||||||
 | 
					    addQuestKey,
 | 
				
			||||||
 | 
					    completeQuest,
 | 
				
			||||||
 | 
					    giveKeyChainMissionReward,
 | 
				
			||||||
 | 
					    giveKeyChainStageTriggered
 | 
				
			||||||
 | 
					} from "@/src/services/questService";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportKeys } from "warframe-public-export-plus";
 | 
					import { ExportKeys } from "warframe-public-export-plus";
 | 
				
			||||||
@ -9,13 +13,17 @@ import { ExportKeys } from "warframe-public-export-plus";
 | 
				
			|||||||
export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
					export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const operation = req.query.operation as
 | 
					    const operation = req.query.operation as
 | 
				
			||||||
        | "unlockAll"
 | 
					 | 
				
			||||||
        | "completeAll"
 | 
					        | "completeAll"
 | 
				
			||||||
        | "ResetAll"
 | 
					        | "resetAll"
 | 
				
			||||||
        | "completeAllUnlocked"
 | 
					        | "giveAll"
 | 
				
			||||||
        | "updateKey"
 | 
					        | "completeKey"
 | 
				
			||||||
        | "giveAll";
 | 
					        | "deleteKey"
 | 
				
			||||||
    const questKeyUpdate = req.body as IUpdateQuestRequest["QuestKeys"];
 | 
					        | "resetKey"
 | 
				
			||||||
 | 
					        | "prevStage"
 | 
				
			||||||
 | 
					        | "nextStage"
 | 
				
			||||||
 | 
					        | "setInactive";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const questItemType = req.query.itemType as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const allQuestKeys: string[] = [];
 | 
					    const allQuestKeys: string[] = [];
 | 
				
			||||||
    for (const [k, v] of Object.entries(ExportKeys)) {
 | 
					    for (const [k, v] of Object.entries(ExportKeys)) {
 | 
				
			||||||
@ -26,47 +34,15 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch (operation) {
 | 
					    switch (operation) {
 | 
				
			||||||
        case "updateKey": {
 | 
					 | 
				
			||||||
            //TODO: if this is intended to be used, one needs to add a updateQuestKeyMultiple, the game does never intend to do it, so it errors for multiple keys.
 | 
					 | 
				
			||||||
            await updateQuestKey(inventory, questKeyUpdate);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        case "unlockAll": {
 | 
					 | 
				
			||||||
            for (const questKey of allQuestKeys) {
 | 
					 | 
				
			||||||
                addQuestKey(inventory, { ItemType: questKey, Completed: false, unlock: true, Progress: [] });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        case "completeAll": {
 | 
					        case "completeAll": {
 | 
				
			||||||
            logger.info("completing all quests..");
 | 
					            if (allQuestKeys.includes(questItemType)) {
 | 
				
			||||||
            for (const questKey of allQuestKeys) {
 | 
					                for (const questKey of inventory.QuestKeys) {
 | 
				
			||||||
                try {
 | 
					                    await completeQuest(inventory, questKey.ItemType);
 | 
				
			||||||
                    await completeQuest(inventory, questKey);
 | 
					 | 
				
			||||||
                } catch (error) {
 | 
					 | 
				
			||||||
                    if (error instanceof Error) {
 | 
					 | 
				
			||||||
                        logger.error(
 | 
					 | 
				
			||||||
                            `Something went wrong completing quest ${questKey}, probably could not add some item`
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                        logger.error(error.message);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                //Skip "Watch The Maker"
 | 
					 | 
				
			||||||
                if (questKey === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") {
 | 
					 | 
				
			||||||
                    addString(
 | 
					 | 
				
			||||||
                        inventory.NodeIntrosCompleted,
 | 
					 | 
				
			||||||
                        "/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (questKey === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") {
 | 
					 | 
				
			||||||
                    inventory.ArchwingEnabled = true;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case "ResetAll": {
 | 
					        case "resetAll": {
 | 
				
			||||||
            logger.info("resetting all quests..");
 | 
					 | 
				
			||||||
            for (const questKey of inventory.QuestKeys) {
 | 
					            for (const questKey of inventory.QuestKeys) {
 | 
				
			||||||
                questKey.Completed = false;
 | 
					                questKey.Completed = false;
 | 
				
			||||||
                questKey.Progress = [];
 | 
					                questKey.Progress = [];
 | 
				
			||||||
@ -75,40 +51,110 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            inventory.ActiveQuest = "";
 | 
					            inventory.ActiveQuest = "";
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case "completeAllUnlocked": {
 | 
					 | 
				
			||||||
            logger.info("completing all unlocked quests..");
 | 
					 | 
				
			||||||
            for (const questKey of inventory.QuestKeys) {
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    await completeQuest(inventory, questKey.ItemType);
 | 
					 | 
				
			||||||
                } catch (error) {
 | 
					 | 
				
			||||||
                    if (error instanceof Error) {
 | 
					 | 
				
			||||||
                        logger.error(
 | 
					 | 
				
			||||||
                            `Something went wrong completing quest ${questKey.ItemType}, probably could not add some item`
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                        logger.error(error.message);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                //Skip "Watch The Maker"
 | 
					 | 
				
			||||||
                if (questKey.ItemType === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") {
 | 
					 | 
				
			||||||
                    addString(
 | 
					 | 
				
			||||||
                        inventory.NodeIntrosCompleted,
 | 
					 | 
				
			||||||
                        "/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (questKey.ItemType === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") {
 | 
					 | 
				
			||||||
                    inventory.ArchwingEnabled = true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        case "giveAll": {
 | 
					        case "giveAll": {
 | 
				
			||||||
            for (const questKey of allQuestKeys) {
 | 
					            allQuestKeys.forEach(questKey => addQuestKey(inventory, { ItemType: questKey }));
 | 
				
			||||||
                addQuestKey(inventory, { ItemType: questKey });
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case "deleteKey": {
 | 
				
			||||||
 | 
					            const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
 | 
				
			||||||
 | 
					            if (!questKey) {
 | 
				
			||||||
 | 
					                logger.error(`Quest key not found in inventory: ${questItemType}`);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            inventory.QuestKeys.pull({ ItemType: questItemType });
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case "completeKey": {
 | 
				
			||||||
 | 
					            if (allQuestKeys.includes(questItemType)) {
 | 
				
			||||||
 | 
					                const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
 | 
				
			||||||
 | 
					                if (!questKey) {
 | 
				
			||||||
 | 
					                    logger.error(`Quest key not found in inventory: ${questItemType}`);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await completeQuest(inventory, questItemType);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        case "resetKey": {
 | 
				
			||||||
 | 
					            if (allQuestKeys.includes(questItemType)) {
 | 
				
			||||||
 | 
					                const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
 | 
				
			||||||
 | 
					                if (!questKey) {
 | 
				
			||||||
 | 
					                    logger.error(`Quest key not found in inventory: ${questItemType}`);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                questKey.Completed = false;
 | 
				
			||||||
 | 
					                questKey.Progress = [];
 | 
				
			||||||
 | 
					                questKey.CompletionDate = undefined;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case "prevStage": {
 | 
				
			||||||
 | 
					            if (allQuestKeys.includes(questItemType)) {
 | 
				
			||||||
 | 
					                const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
 | 
				
			||||||
 | 
					                if (!questKey) {
 | 
				
			||||||
 | 
					                    logger.error(`Quest key not found in inventory: ${questItemType}`);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!questKey.Progress) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (questKey.Completed) {
 | 
				
			||||||
 | 
					                    questKey.Completed = false;
 | 
				
			||||||
 | 
					                    questKey.CompletionDate = undefined;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                questKey.Progress.pop();
 | 
				
			||||||
 | 
					                const stage = questKey.Progress.length - 1;
 | 
				
			||||||
 | 
					                if (stage > 0) {
 | 
				
			||||||
 | 
					                    await giveKeyChainStageTriggered(inventory, {
 | 
				
			||||||
 | 
					                        KeyChain: questKey.ItemType,
 | 
				
			||||||
 | 
					                        ChainStage: stage
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case "nextStage": {
 | 
				
			||||||
 | 
					            if (allQuestKeys.includes(questItemType)) {
 | 
				
			||||||
 | 
					                const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
 | 
				
			||||||
 | 
					                const questManifest = ExportKeys[questItemType];
 | 
				
			||||||
 | 
					                if (!questKey) {
 | 
				
			||||||
 | 
					                    logger.error(`Quest key not found in inventory: ${questItemType}`);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!questKey.Progress) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const currentStage = questKey.Progress.length;
 | 
				
			||||||
 | 
					                if (currentStage + 1 == questManifest.chainStages?.length) {
 | 
				
			||||||
 | 
					                    logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`);
 | 
				
			||||||
 | 
					                    await completeQuest(inventory, questKey.ItemType);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    const progress = {
 | 
				
			||||||
 | 
					                        c: questManifest.chainStages![currentStage].key ? -1 : 0,
 | 
				
			||||||
 | 
					                        i: false,
 | 
				
			||||||
 | 
					                        m: false,
 | 
				
			||||||
 | 
					                        b: []
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                    questKey.Progress.push(progress);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    await giveKeyChainStageTriggered(inventory, {
 | 
				
			||||||
 | 
					                        KeyChain: questKey.ItemType,
 | 
				
			||||||
 | 
					                        ChainStage: currentStage
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (currentStage > 0) {
 | 
				
			||||||
 | 
					                        await giveKeyChainMissionReward(inventory, {
 | 
				
			||||||
 | 
					                            KeyChain: questKey.ItemType,
 | 
				
			||||||
 | 
					                            ChainStage: currentStage - 1
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case "setInactive":
 | 
				
			||||||
 | 
					            inventory.ActiveQuest = "";
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ import { getInventory } from "@/src/services/inventoryService";
 | 
				
			|||||||
export const popArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
 | 
					export const popArchonCrystalUpgradeController: 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 suit = inventory.Suits.find(suit => suit._id.toString() == (req.query.oid as string));
 | 
					    const suit = inventory.Suits.id(req.query.oid as string);
 | 
				
			||||||
    if (suit && suit.ArchonCrystalUpgrades) {
 | 
					    if (suit && suit.ArchonCrystalUpgrades) {
 | 
				
			||||||
        suit.ArchonCrystalUpgrades = suit.ArchonCrystalUpgrades.filter(
 | 
					        suit.ArchonCrystalUpgrades = suit.ArchonCrystalUpgrades.filter(
 | 
				
			||||||
            x => x.UpgradeType != (req.query.type as string)
 | 
					            x => x.UpgradeType != (req.query.type as string)
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ import { getInventory } from "@/src/services/inventoryService";
 | 
				
			|||||||
export const pushArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
 | 
					export const pushArchonCrystalUpgradeController: 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 suit = inventory.Suits.find(suit => suit._id.toString() == (req.query.oid as string));
 | 
					    const suit = inventory.Suits.id(req.query.oid as string);
 | 
				
			||||||
    if (suit) {
 | 
					    if (suit) {
 | 
				
			||||||
        suit.ArchonCrystalUpgrades ??= [];
 | 
					        suit.ArchonCrystalUpgrades ??= [];
 | 
				
			||||||
        const count = (req.query.count as number | undefined) ?? 1;
 | 
					        const count = (req.query.count as number | undefined) ?? 1;
 | 
				
			||||||
 | 
				
			|||||||
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