merge upstream
This commit is contained in:
		
						commit
						2e6188109c
					
				
							
								
								
									
										36
									
								
								.eslintrc
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								.eslintrc
									
									
									
									
									
								
							@ -1,36 +1,46 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    "plugins": ["@typescript-eslint", "prettier", "import"],
 | 
				
			||||||
    "extends": [
 | 
					    "extends": [
 | 
				
			||||||
        "eslint:recommended",
 | 
					        "eslint:recommended",
 | 
				
			||||||
        "plugin:@typescript-eslint/recommended",
 | 
					        "plugin:@typescript-eslint/recommended",
 | 
				
			||||||
        "plugin:@typescript-eslint/recommended-requiring-type-checking"
 | 
					        "plugin:@typescript-eslint/recommended-requiring-type-checking",
 | 
				
			||||||
 | 
					        "plugin:import/recommended",
 | 
				
			||||||
 | 
					        "plugin:import/typescript"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "plugins": ["@typescript-eslint", "prettier"],
 | 
					 | 
				
			||||||
    "env": {
 | 
					    "env": {
 | 
				
			||||||
        "browser": true,
 | 
					        "browser": true,
 | 
				
			||||||
        "es6": true,
 | 
					        "es6": true,
 | 
				
			||||||
        "node": true
 | 
					        "node": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "rules": {
 | 
					    "rules": {
 | 
				
			||||||
        "@typescript-eslint/explicit-function-return-type": "warn",
 | 
					        "@typescript-eslint/explicit-function-return-type": "error",
 | 
				
			||||||
        "@typescript-eslint/restrict-template-expressions": "warn",
 | 
					        "@typescript-eslint/restrict-template-expressions": "error",
 | 
				
			||||||
        "@typescript-eslint/restrict-plus-operands": "warn",
 | 
					        "@typescript-eslint/restrict-plus-operands": "error",
 | 
				
			||||||
        "@typescript-eslint/no-unsafe-member-access": "warn",
 | 
					        "@typescript-eslint/no-unsafe-member-access": "error",
 | 
				
			||||||
        "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
 | 
					        "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
 | 
				
			||||||
        "@typescript-eslint/no-unsafe-argument": "error",
 | 
					        "@typescript-eslint/no-unsafe-argument": "error",
 | 
				
			||||||
        "@typescript-eslint/no-unsafe-call": "warn",
 | 
					        "@typescript-eslint/no-unsafe-call": "error",
 | 
				
			||||||
        "@typescript-eslint/no-unsafe-assignment": "warn",
 | 
					        "@typescript-eslint/no-unsafe-assignment": "error",
 | 
				
			||||||
        "@typescript-eslint/no-explicit-any": "warn",
 | 
					        "@typescript-eslint/no-explicit-any": "error",
 | 
				
			||||||
        "no-loss-of-precision": "warn",
 | 
					        "no-loss-of-precision": "error",
 | 
				
			||||||
        "@typescript-eslint/no-unnecessary-condition": "warn",
 | 
					        "@typescript-eslint/no-unnecessary-condition": "error",
 | 
				
			||||||
        "@typescript-eslint/no-base-to-string": "off",
 | 
					        "@typescript-eslint/no-base-to-string": "off",
 | 
				
			||||||
        "no-case-declarations": "error",
 | 
					        "no-case-declarations": "error",
 | 
				
			||||||
        "prettier/prettier": "error",
 | 
					        "prettier/prettier": "error",
 | 
				
			||||||
        "no-mixed-spaces-and-tabs": "error",
 | 
					        "no-mixed-spaces-and-tabs": "error",
 | 
				
			||||||
        "require-await": "off",
 | 
					        "@typescript-eslint/require-await": "error",
 | 
				
			||||||
        "@typescript-eslint/require-await": "error"
 | 
					        "import/no-named-as-default-member": "off",
 | 
				
			||||||
 | 
					        "import/no-cycle": "warn"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "parser": "@typescript-eslint/parser",
 | 
					    "parser": "@typescript-eslint/parser",
 | 
				
			||||||
    "parserOptions": {
 | 
					    "parserOptions": {
 | 
				
			||||||
        "project": "./tsconfig.json"
 | 
					        "project": "./tsconfig.json"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "settings": {
 | 
				
			||||||
 | 
					        "import/extensions": [ ".ts" ],
 | 
				
			||||||
 | 
					        "import/resolver": {
 | 
				
			||||||
 | 
					            "typescript": true,
 | 
				
			||||||
 | 
					            "node": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@ -1,6 +1,7 @@
 | 
				
			|||||||
name: Build
 | 
					name: Build
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
    push: {}
 | 
					    push:
 | 
				
			||||||
 | 
					        branches: ["main"]
 | 
				
			||||||
    pull_request: {}
 | 
					    pull_request: {}
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
    build:
 | 
					    build:
 | 
				
			||||||
@ -18,6 +19,7 @@ jobs:
 | 
				
			|||||||
            - run: npm run lint:ci
 | 
					            - run: npm run lint:ci
 | 
				
			||||||
            - run: npm run prettier
 | 
					            - run: npm run prettier
 | 
				
			||||||
            - run: npm run update-translations
 | 
					            - run: npm run update-translations
 | 
				
			||||||
 | 
					            - run: npm run fix-imports
 | 
				
			||||||
            - name: Fail if there are uncommitted changes
 | 
					            - name: Fail if there are uncommitted changes
 | 
				
			||||||
              run: |
 | 
					              run: |
 | 
				
			||||||
                  if [[ -n "$(git status --porcelain)" ]]; then
 | 
					                  if [[ -n "$(git status --porcelain)" ]]; then
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										25
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							@ -4,9 +4,9 @@ on:
 | 
				
			|||||||
        branches:
 | 
					        branches:
 | 
				
			||||||
            - main
 | 
					            - main
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
    docker:
 | 
					    docker-amd64:
 | 
				
			||||||
        if: github.repository == 'OpenWF/SpaceNinjaServer'
 | 
					        if: github.repository == 'OpenWF/SpaceNinjaServer'
 | 
				
			||||||
        runs-on: ubuntu-latest
 | 
					        runs-on: amd64
 | 
				
			||||||
        steps:
 | 
					        steps:
 | 
				
			||||||
            - name: Set up Docker buildx
 | 
					            - name: Set up Docker buildx
 | 
				
			||||||
              uses: docker/setup-buildx-action@v3
 | 
					              uses: docker/setup-buildx-action@v3
 | 
				
			||||||
@ -18,8 +18,27 @@ jobs:
 | 
				
			|||||||
            - name: Build and push
 | 
					            - name: Build and push
 | 
				
			||||||
              uses: docker/build-push-action@v6
 | 
					              uses: docker/build-push-action@v6
 | 
				
			||||||
              with:
 | 
					              with:
 | 
				
			||||||
                  platforms: linux/amd64,linux/arm64
 | 
					                  platforms: linux/amd64
 | 
				
			||||||
                  push: true
 | 
					                  push: true
 | 
				
			||||||
                  tags: |
 | 
					                  tags: |
 | 
				
			||||||
                      openwf/spaceninjaserver:latest
 | 
					                      openwf/spaceninjaserver:latest
 | 
				
			||||||
                      openwf/spaceninjaserver:${{ github.sha }}
 | 
					                      openwf/spaceninjaserver:${{ github.sha }}
 | 
				
			||||||
 | 
					    docker-arm64:
 | 
				
			||||||
 | 
					        if: github.repository == 'OpenWF/SpaceNinjaServer'
 | 
				
			||||||
 | 
					        runs-on: arm64
 | 
				
			||||||
 | 
					        steps:
 | 
				
			||||||
 | 
					            - name: Set up Docker buildx
 | 
				
			||||||
 | 
					              uses: docker/setup-buildx-action@v3
 | 
				
			||||||
 | 
					            - name: Log in to container registry
 | 
				
			||||||
 | 
					              uses: docker/login-action@v3
 | 
				
			||||||
 | 
					              with:
 | 
				
			||||||
 | 
					                  username: openwf
 | 
				
			||||||
 | 
					                  password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
				
			||||||
 | 
					            - name: Build and push
 | 
				
			||||||
 | 
					              uses: docker/build-push-action@v6
 | 
				
			||||||
 | 
					              with:
 | 
				
			||||||
 | 
					                  platforms: linux/arm64
 | 
				
			||||||
 | 
					                  push: true
 | 
				
			||||||
 | 
					                  tags: |
 | 
				
			||||||
 | 
					                      openwf/spaceninjaserver:latest-arm64
 | 
				
			||||||
 | 
					                      openwf/spaceninjaserver:${{ github.sha }}-arm64
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@ -8,8 +8,7 @@
 | 
				
			|||||||
      "type": "node",
 | 
					      "type": "node",
 | 
				
			||||||
      "request": "launch",
 | 
					      "request": "launch",
 | 
				
			||||||
      "name": "Debug and Watch",
 | 
					      "name": "Debug and Watch",
 | 
				
			||||||
      "runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"],
 | 
					      "args": ["${workspaceFolder}/scripts/dev.js"],
 | 
				
			||||||
      "args": ["${workspaceFolder}/src/index.ts"],
 | 
					 | 
				
			||||||
      "console": "integratedTerminal"
 | 
					      "console": "integratedTerminal"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										17
									
								
								AGENTS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								AGENTS.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					## In General
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Prerequisites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Use `npm i` or `npm ci` to install all dependencies.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Testing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Use `npm run verify` to verify that your changes pass TypeScript's checks.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Formatting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Use `npm run prettier` to ensure your formatting matches the expected format. Failing to do so will cause CI failure.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## WebUI Specific
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT produce non-English strings; we want them to be translated by humans who can understand the full context.
 | 
				
			||||||
							
								
								
									
										52
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								Dockerfile
									
									
									
									
									
								
							@ -1,53 +1,11 @@
 | 
				
			|||||||
FROM node:18-alpine3.19
 | 
					FROM node:24-alpine3.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF
 | 
					RUN apk add --no-cache bash jq
 | 
				
			||||||
ENV APP_MY_ADDRESS=localhost
 | 
					 | 
				
			||||||
ENV APP_HTTP_PORT=80
 | 
					 | 
				
			||||||
ENV APP_HTTPS_PORT=443
 | 
					 | 
				
			||||||
ENV APP_AUTO_CREATE_ACCOUNT=true
 | 
					 | 
				
			||||||
ENV APP_SKIP_TUTORIAL=false
 | 
					 | 
				
			||||||
ENV APP_SKIP_ALL_DIALOGUE=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_ALL_SCANS=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_ALL_MISSIONS=false
 | 
					 | 
				
			||||||
ENV APP_INFINITE_CREDITS=false
 | 
					 | 
				
			||||||
ENV APP_INFINITE_PLATINUM=false
 | 
					 | 
				
			||||||
ENV APP_INFINITE_ENDO=false
 | 
					 | 
				
			||||||
ENV APP_INFINITE_REGAL_AYA=false
 | 
					 | 
				
			||||||
ENV APP_INFINITE_HELMINTH_MATERIALS=false
 | 
					 | 
				
			||||||
ENV APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS=false
 | 
					 | 
				
			||||||
ENV APP_DONT_SUBTRACT_VOIDTRACES=false
 | 
					 | 
				
			||||||
ENV APP_DONT_SUBTRACT_CONSUMABLES=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_ALL_SKINS=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_ALL_CAPTURA_SCENES=false
 | 
					 | 
				
			||||||
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_EXILUS_EVERYWHERE=false
 | 
					 | 
				
			||||||
ENV APP_UNLOCK_ARCANES_EVERYWHERE=false
 | 
					 | 
				
			||||||
ENV APP_NO_DAILY_FOCUS_LIMIT=false
 | 
					 | 
				
			||||||
ENV APP_NO_ARGON_CRYSTAL_DECAY=false
 | 
					 | 
				
			||||||
ENV APP_NO_MASTERY_RANK_UP_COOLDOWN=false
 | 
					 | 
				
			||||||
ENV APP_NO_VENDOR_PURCHASE_LIMITS=true
 | 
					 | 
				
			||||||
ENV APP_NO_DEATH_MARKS=false
 | 
					 | 
				
			||||||
ENV APP_NO_KIM_COOLDOWNS=false
 | 
					 | 
				
			||||||
ENV APP_SYNDICATE_MISSIONS_REPEATABLE=false
 | 
					 | 
				
			||||||
ENV APP_INSTANT_FINISH_RIVEN_CHALLENGE=false
 | 
					 | 
				
			||||||
ENV APP_INSTANT_RESOURCE_EXTRACTOR_DRONES=false
 | 
					 | 
				
			||||||
ENV APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE=false
 | 
					 | 
				
			||||||
ENV APP_SKIP_CLAN_KEY_CRAFTING=false
 | 
					 | 
				
			||||||
ENV APP_NO_DOJO_ROOM_BUILD_STAGE=false
 | 
					 | 
				
			||||||
ENV APP_NO_DECO_BUILD_STAGE=false
 | 
					 | 
				
			||||||
ENV APP_FAST_DOJO_ROOM_DESTRUCTION=false
 | 
					 | 
				
			||||||
ENV APP_NO_DOJO_RESEARCH_COSTS=false
 | 
					 | 
				
			||||||
ENV APP_NO_DOJO_RESEARCH_TIME=false
 | 
					 | 
				
			||||||
ENV APP_FAST_CLAN_ASCENSION=false
 | 
					 | 
				
			||||||
ENV APP_SPOOF_MASTERY_RANK=-1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apk add --no-cache bash sed wget jq
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY . /app
 | 
					COPY . /app
 | 
				
			||||||
WORKDIR /app
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN npm i --omit=dev
 | 
				
			||||||
 | 
					RUN npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
 | 
					ENTRYPOINT ["/app/docker-entrypoint.sh"]
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
 | 
				
			|||||||
- `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.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
 | 
					- `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
 | 
				
			||||||
- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
 | 
					- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
 | 
				
			||||||
 | 
					- `worldState.duviriOverride` can be set to `joy`, `anger`, `envy`, `sorrow`, or `fear` to lock the Duviri spiral.
 | 
				
			||||||
- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
 | 
					- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
 | 
				
			||||||
  - `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9
 | 
					  - `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9
 | 
				
			||||||
  - `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8
 | 
					  - `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8
 | 
				
			||||||
@ -33,3 +34,5 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
 | 
				
			|||||||
  - `RadioLegion2Syndicate` for The Emissary
 | 
					  - `RadioLegion2Syndicate` for The Emissary
 | 
				
			||||||
  - `RadioLegionIntermissionSyndicate` for Intermission I
 | 
					  - `RadioLegionIntermissionSyndicate` for Intermission I
 | 
				
			||||||
  - `RadioLegionSyndicate` for The Wolf of Saturn Six
 | 
					  - `RadioLegionSyndicate` for The Wolf of Saturn Six
 | 
				
			||||||
 | 
					- `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
 | 
				
			||||||
 | 
					- `worldState.circuitGameModes` can be set to an array of game modes which will override the otherwise-random pattern in The Circuit. Valid element values are `Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, and `Alchemy`.
 | 
				
			||||||
 | 
				
			|||||||
@ -13,13 +13,16 @@
 | 
				
			|||||||
  "skipTutorial": false,
 | 
					  "skipTutorial": false,
 | 
				
			||||||
  "skipAllDialogue": false,
 | 
					  "skipAllDialogue": false,
 | 
				
			||||||
  "unlockAllScans": false,
 | 
					  "unlockAllScans": false,
 | 
				
			||||||
  "unlockAllMissions": false,
 | 
					 | 
				
			||||||
  "infiniteCredits": false,
 | 
					  "infiniteCredits": false,
 | 
				
			||||||
  "infinitePlatinum": false,
 | 
					  "infinitePlatinum": false,
 | 
				
			||||||
  "infiniteEndo": false,
 | 
					  "infiniteEndo": false,
 | 
				
			||||||
  "infiniteRegalAya": false,
 | 
					  "infiniteRegalAya": false,
 | 
				
			||||||
  "infiniteHelminthMaterials": false,
 | 
					  "infiniteHelminthMaterials": false,
 | 
				
			||||||
  "claimingBlueprintRefundsIngredients": false,
 | 
					  "claimingBlueprintRefundsIngredients": false,
 | 
				
			||||||
 | 
					  "dontSubtractPurchaseCreditCost": false,
 | 
				
			||||||
 | 
					  "dontSubtractPurchasePlatinumCost": false,
 | 
				
			||||||
 | 
					  "dontSubtractPurchaseItemCost": false,
 | 
				
			||||||
 | 
					  "dontSubtractPurchaseStandingCost": false,
 | 
				
			||||||
  "dontSubtractVoidTraces": false,
 | 
					  "dontSubtractVoidTraces": false,
 | 
				
			||||||
  "dontSubtractConsumables": false,
 | 
					  "dontSubtractConsumables": false,
 | 
				
			||||||
  "unlockAllShipFeatures": false,
 | 
					  "unlockAllShipFeatures": false,
 | 
				
			||||||
@ -35,10 +38,14 @@
 | 
				
			|||||||
  "noDailyFocusLimit": false,
 | 
					  "noDailyFocusLimit": false,
 | 
				
			||||||
  "noArgonCrystalDecay": false,
 | 
					  "noArgonCrystalDecay": false,
 | 
				
			||||||
  "noMasteryRankUpCooldown": false,
 | 
					  "noMasteryRankUpCooldown": false,
 | 
				
			||||||
  "noVendorPurchaseLimits": true,
 | 
					  "noVendorPurchaseLimits": false,
 | 
				
			||||||
  "noDeathMarks": false,
 | 
					  "noDeathMarks": false,
 | 
				
			||||||
  "noKimCooldowns": false,
 | 
					  "noKimCooldowns": false,
 | 
				
			||||||
 | 
					  "fullyStockedVendors": false,
 | 
				
			||||||
 | 
					  "baroAlwaysAvailable": false,
 | 
				
			||||||
 | 
					  "baroFullyStocked": false,
 | 
				
			||||||
  "syndicateMissionsRepeatable": false,
 | 
					  "syndicateMissionsRepeatable": false,
 | 
				
			||||||
 | 
					  "unlockAllProfitTakerStages": false,
 | 
				
			||||||
  "instantFinishRivenChallenge": false,
 | 
					  "instantFinishRivenChallenge": false,
 | 
				
			||||||
  "instantResourceExtractorDrones": false,
 | 
					  "instantResourceExtractorDrones": false,
 | 
				
			||||||
  "noResourceExtractorDronesDamage": false,
 | 
					  "noResourceExtractorDronesDamage": false,
 | 
				
			||||||
@ -49,14 +56,33 @@
 | 
				
			|||||||
  "noDojoResearchCosts": false,
 | 
					  "noDojoResearchCosts": false,
 | 
				
			||||||
  "noDojoResearchTime": false,
 | 
					  "noDojoResearchTime": false,
 | 
				
			||||||
  "fastClanAscension": false,
 | 
					  "fastClanAscension": false,
 | 
				
			||||||
 | 
					  "missionsCanGiveAllRelics": false,
 | 
				
			||||||
 | 
					  "unlockAllSimarisResearchEntries": false,
 | 
				
			||||||
 | 
					  "disableDailyTribute": false,
 | 
				
			||||||
  "spoofMasteryRank": -1,
 | 
					  "spoofMasteryRank": -1,
 | 
				
			||||||
 | 
					  "relicRewardItemCountMultiplier": 1,
 | 
				
			||||||
 | 
					  "nightwaveStandingMultiplier": 1,
 | 
				
			||||||
 | 
					  "unfaithfulBugFixes": {
 | 
				
			||||||
 | 
					    "ignore1999LastRegionPlayed": false,
 | 
				
			||||||
 | 
					    "fixXtraCheeseTimer": false
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  "worldState": {
 | 
					  "worldState": {
 | 
				
			||||||
    "creditBoost": false,
 | 
					    "creditBoost": false,
 | 
				
			||||||
    "affinityBoost": false,
 | 
					    "affinityBoost": false,
 | 
				
			||||||
    "resourceBoost": false,
 | 
					    "resourceBoost": false,
 | 
				
			||||||
    "starDays": true,
 | 
					    "starDays": true,
 | 
				
			||||||
 | 
					    "galleonOfGhouls": 0,
 | 
				
			||||||
    "eidolonOverride": "",
 | 
					    "eidolonOverride": "",
 | 
				
			||||||
    "vallisOverride": "",
 | 
					    "vallisOverride": "",
 | 
				
			||||||
    "nightwaveOverride": ""
 | 
					    "duviriOverride": "",
 | 
				
			||||||
 | 
					    "nightwaveOverride": "",
 | 
				
			||||||
 | 
					    "allTheFissures": "",
 | 
				
			||||||
 | 
					    "circuitGameModes": null,
 | 
				
			||||||
 | 
					    "darvoStockMultiplier": 1,
 | 
				
			||||||
 | 
					    "varziaOverride": "",
 | 
				
			||||||
 | 
					    "varziaFullyStocked": false
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "dev": {
 | 
				
			||||||
 | 
					    "keepVendorsExpired": false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,62 +1,20 @@
 | 
				
			|||||||
services:
 | 
					services:
 | 
				
			||||||
    spaceninjaserver:
 | 
					    spaceninjaserver:
 | 
				
			||||||
        # build: .
 | 
					        # The image to use. If you have an ARM CPU, replace 'latest' with 'latest-arm64'.
 | 
				
			||||||
        image: openwf/spaceninjaserver:latest
 | 
					        image: openwf/spaceninjaserver:latest
 | 
				
			||||||
        environment:
 | 
					 | 
				
			||||||
            APP_MONGODB_URL: mongodb://openwfagent:spaceninjaserver@mongodb:27017/
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Following environment variables are set to default image values.
 | 
					 | 
				
			||||||
            # Uncomment to edit.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # APP_MY_ADDRESS: localhost
 | 
					 | 
				
			||||||
            # APP_HTTP_PORT: 80
 | 
					 | 
				
			||||||
            # APP_HTTPS_PORT: 443
 | 
					 | 
				
			||||||
            # APP_AUTO_CREATE_ACCOUNT: true
 | 
					 | 
				
			||||||
            # APP_SKIP_TUTORIAL: false
 | 
					 | 
				
			||||||
            # APP_SKIP_ALL_DIALOGUE: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_ALL_SCANS: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_ALL_MISSIONS: false
 | 
					 | 
				
			||||||
            # APP_INFINITE_CREDITS: false
 | 
					 | 
				
			||||||
            # APP_INFINITE_PLATINUM: false
 | 
					 | 
				
			||||||
            # APP_INFINITE_ENDO: false
 | 
					 | 
				
			||||||
            # APP_INFINITE_REGAL_AYA: false
 | 
					 | 
				
			||||||
            # APP_INFINITE_HELMINTH_MATERIALS: false
 | 
					 | 
				
			||||||
            # APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS: false
 | 
					 | 
				
			||||||
            # APP_DONT_SUBTRACT_VOIDTRACES: false
 | 
					 | 
				
			||||||
            # APP_DONT_SUBTRACT_CONSUMABLES: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_ALL_SHIP_FEATURES: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_ALL_SHIP_DECORATIONS: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_ALL_FLAVOUR_ITEMS: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_ALL_SKINS: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_ALL_CAPTURA_SCENES: false
 | 
					 | 
				
			||||||
            # APP_UNIVERSAL_POLARITY_EVERYWHERE: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_EXILUS_EVERYWHERE: false
 | 
					 | 
				
			||||||
            # APP_UNLOCK_ARCANES_EVERYWHERE: false
 | 
					 | 
				
			||||||
            # APP_NO_DAILY_FOCUS_LIMIT: false
 | 
					 | 
				
			||||||
            # APP_NO_ARGON_CRYSTAL_DECAY: false
 | 
					 | 
				
			||||||
            # APP_NO_MASTERY_RANK_UP_COOLDOWN: false
 | 
					 | 
				
			||||||
            # APP_NO_VENDOR_PURCHASE_LIMITS: true
 | 
					 | 
				
			||||||
            # APP_NO_DEATH_MARKS: false
 | 
					 | 
				
			||||||
            # APP_NO_KIM_COOLDOWNS: false
 | 
					 | 
				
			||||||
            # APP_SYNDICATE_MISSIONS_REPEATABLE: false
 | 
					 | 
				
			||||||
            # APP_INSTANT_FINISH_RIVEN_CHALLENGE: false
 | 
					 | 
				
			||||||
            # APP_INSTANT_RESOURCE_EXTRACTOR_DRONES: false
 | 
					 | 
				
			||||||
            # APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE: false
 | 
					 | 
				
			||||||
            # APP_SKIP_CLAN_KEY_CRAFTING: false
 | 
					 | 
				
			||||||
            # APP_NO_DOJO_ROOM_BUILD_STAGE: false
 | 
					 | 
				
			||||||
            # APP_NO_DECO_BUILD_STAGE: false
 | 
					 | 
				
			||||||
            # APP_FAST_DOJO_ROOM_DESTRUCTION: false
 | 
					 | 
				
			||||||
            # APP_NO_DOJO_RESEARCH_COSTS: false
 | 
					 | 
				
			||||||
            # APP_NO_DOJO_RESEARCH_TIME: false
 | 
					 | 
				
			||||||
            # APP_FAST_CLAN_ASCENSION: false
 | 
					 | 
				
			||||||
            # APP_SPOOF_MASTERY_RANK: -1
 | 
					 | 
				
			||||||
        volumes:
 | 
					        volumes:
 | 
				
			||||||
            - ./docker-data/static:/app/static/data
 | 
					            - ./docker-data/conf:/app/conf
 | 
				
			||||||
 | 
					            - ./docker-data/static-data:/app/static/data
 | 
				
			||||||
            - ./docker-data/logs:/app/logs
 | 
					            - ./docker-data/logs:/app/logs
 | 
				
			||||||
        ports:
 | 
					        ports:
 | 
				
			||||||
            - 80:80
 | 
					            - 80:80
 | 
				
			||||||
            - 443:443
 | 
					            - 443:443
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by removing "image" above and adding this:
 | 
				
			||||||
 | 
					        #build: .
 | 
				
			||||||
 | 
					        # Works best when using `docker-compose up --force-recreate --build`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        depends_on:
 | 
					        depends_on:
 | 
				
			||||||
            - mongodb
 | 
					            - mongodb
 | 
				
			||||||
    mongodb:
 | 
					    mongodb:
 | 
				
			||||||
@ -66,3 +24,4 @@ services:
 | 
				
			|||||||
            MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
 | 
					            MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
 | 
				
			||||||
        volumes:
 | 
					        volumes:
 | 
				
			||||||
            - ./docker-data/database:/data/db
 | 
					            - ./docker-data/database:/data/db
 | 
				
			||||||
 | 
					        command: mongod --quiet --logpath /dev/null
 | 
				
			||||||
 | 
				
			|||||||
@ -1,24 +1,8 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Set up the configuration file using environment variables.
 | 
					if [ ! -f conf/config.json ]; then
 | 
				
			||||||
echo '{
 | 
						jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config.json.example > /app/conf/config.json
 | 
				
			||||||
	"logger": {
 | 
					fi
 | 
				
			||||||
	  "files": true,
 | 
					 | 
				
			||||||
	  "level": "trace",
 | 
					 | 
				
			||||||
	  "__valid_levels": "fatal, error, warn, info, http, debug, trace"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
' > config.json
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
for config in $(env | grep "APP_")
 | 
					exec npm run start -- --configPath conf/config.json
 | 
				
			||||||
do
 | 
					 | 
				
			||||||
  var=$(echo "${config}" | tr '[:upper:]' '[:lower:]' | sed 's/app_//g' | sed -E 's/_([a-z])/\U\1/g' | sed 's/=.*//g')
 | 
					 | 
				
			||||||
  val=$(echo "${config}" | sed 's/.*=//g')
 | 
					 | 
				
			||||||
  jq --arg variable "$var" --arg value "$val" '.[$variable] += try [$value|fromjson][] catch $value' config.json > config.tmp
 | 
					 | 
				
			||||||
  mv config.tmp config.json
 | 
					 | 
				
			||||||
done
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
npm i --omit=dev
 | 
					 | 
				
			||||||
npm run build
 | 
					 | 
				
			||||||
exec npm run start
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2709
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2709
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										31
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								package.json
									
									
									
									
									
								
							@ -5,19 +5,32 @@
 | 
				
			|||||||
  "main": "index.ts",
 | 
					  "main": "index.ts",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "start": "node --enable-source-maps --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 ",
 | 
					    "build": "tsgo --sourceMap && ncp static/webui build/static/webui",
 | 
				
			||||||
    "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
					    "build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
				
			||||||
 | 
					    "build:dev": "tsgo --sourceMap",
 | 
				
			||||||
 | 
					    "build:dev:tsc": "tsc --incremental --sourceMap",
 | 
				
			||||||
 | 
					    "build-and-start": "npm run build && npm run start",
 | 
				
			||||||
 | 
					    "build-and-start:bun": "npm run verify && npm run bun-run",
 | 
				
			||||||
 | 
					    "dev": "node scripts/dev.js",
 | 
				
			||||||
 | 
					    "dev:bun": "bun scripts/dev.js",
 | 
				
			||||||
    "verify": "tsgo --noEmit",
 | 
					    "verify": "tsgo --noEmit",
 | 
				
			||||||
 | 
					    "bun-run": "bun src/index.ts",
 | 
				
			||||||
    "lint": "eslint --ext .ts .",
 | 
					    "lint": "eslint --ext .ts .",
 | 
				
			||||||
    "lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
 | 
					    "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",
 | 
				
			||||||
 | 
					    "fix-imports": "cd scripts && node fix-imports.js",
 | 
				
			||||||
 | 
					    "fix": "npm run update-translations && npm run fix-imports && npm run prettier"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "license": "GNU",
 | 
					  "license": "GNU",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@types/express": "^5",
 | 
					    "@types/express": "^5",
 | 
				
			||||||
    "@types/morgan": "^1.9.9",
 | 
					    "@types/morgan": "^1.9.9",
 | 
				
			||||||
 | 
					    "@types/websocket": "^1.0.10",
 | 
				
			||||||
 | 
					    "@types/ws": "^8.18.1",
 | 
				
			||||||
 | 
					    "@typescript/native-preview": "^7.0.0-dev.20250625.1",
 | 
				
			||||||
 | 
					    "chokidar": "^4.0.3",
 | 
				
			||||||
    "crc-32": "^1.2.2",
 | 
					    "crc-32": "^1.2.2",
 | 
				
			||||||
    "express": "^5",
 | 
					    "express": "^5",
 | 
				
			||||||
    "json-with-bigint": "^3.4.4",
 | 
					    "json-with-bigint": "^3.4.4",
 | 
				
			||||||
@ -25,19 +38,21 @@
 | 
				
			|||||||
    "morgan": "^1.10.0",
 | 
					    "morgan": "^1.10.0",
 | 
				
			||||||
    "ncp": "^2.0.0",
 | 
					    "ncp": "^2.0.0",
 | 
				
			||||||
    "typescript": "^5.5",
 | 
					    "typescript": "^5.5",
 | 
				
			||||||
    "warframe-public-export-plus": "^0.5.66",
 | 
					    "undici": "^7.10.0",
 | 
				
			||||||
 | 
					    "warframe-public-export-plus": "^0.5.77",
 | 
				
			||||||
    "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",
 | 
				
			||||||
 | 
					    "ws": "^8.18.2"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^8.28.0",
 | 
					    "@typescript-eslint/eslint-plugin": "^8.28.0",
 | 
				
			||||||
    "@typescript-eslint/parser": "^8.28.0",
 | 
					    "@typescript-eslint/parser": "^8.28.0",
 | 
				
			||||||
    "@typescript/native-preview": "^7.0.0-dev.20250523.1",
 | 
					 | 
				
			||||||
    "eslint": "^8",
 | 
					    "eslint": "^8",
 | 
				
			||||||
 | 
					    "eslint-import-resolver-typescript": "^4.4.4",
 | 
				
			||||||
 | 
					    "eslint-plugin-import": "^2.32.0",
 | 
				
			||||||
    "eslint-plugin-prettier": "^5.2.5",
 | 
					    "eslint-plugin-prettier": "^5.2.5",
 | 
				
			||||||
    "prettier": "^3.5.3",
 | 
					    "prettier": "^3.5.3",
 | 
				
			||||||
    "ts-node-dev": "^2.0.0",
 | 
					    "tree-kill": "^1.2.2"
 | 
				
			||||||
    "tsconfig-paths": "^4.2.0"
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										58
									
								
								scripts/dev.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								scripts/dev.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					const { spawn } = require("child_process");
 | 
				
			||||||
 | 
					const chokidar = require("chokidar");
 | 
				
			||||||
 | 
					const kill = require("tree-kill");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let secret = "";
 | 
				
			||||||
 | 
					for (let i = 0; i != 10; ++i) {
 | 
				
			||||||
 | 
					    secret += String.fromCharCode(Math.floor(Math.random() * 26) + 0x41);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const args = [...process.argv].splice(2);
 | 
				
			||||||
 | 
					args.push("--dev");
 | 
				
			||||||
 | 
					args.push("--secret");
 | 
				
			||||||
 | 
					args.push(secret);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let buildproc, runproc;
 | 
				
			||||||
 | 
					const spawnopts = { stdio: "inherit", shell: true };
 | 
				
			||||||
 | 
					function run(changedFile) {
 | 
				
			||||||
 | 
					    if (changedFile) {
 | 
				
			||||||
 | 
					        console.log(`Change to ${changedFile} detected`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (buildproc) {
 | 
				
			||||||
 | 
					        kill(buildproc.pid);
 | 
				
			||||||
 | 
					        buildproc = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (runproc) {
 | 
				
			||||||
 | 
					        kill(runproc.pid);
 | 
				
			||||||
 | 
					        runproc = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const thisbuildproc = spawn("npm", ["run", process.versions.bun ? "verify" : "build:dev"], spawnopts);
 | 
				
			||||||
 | 
					    const thisbuildstart = Date.now();
 | 
				
			||||||
 | 
					    buildproc = thisbuildproc;
 | 
				
			||||||
 | 
					    buildproc.on("exit", code => {
 | 
				
			||||||
 | 
					        if (buildproc !== thisbuildproc) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        buildproc = undefined;
 | 
				
			||||||
 | 
					        if (code === 0) {
 | 
				
			||||||
 | 
					            console.log(`${process.versions.bun ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
 | 
				
			||||||
 | 
					            runproc = spawn("npm", ["run", process.versions.bun ? "bun-run" : "start", "--", ...args], spawnopts);
 | 
				
			||||||
 | 
					            runproc.on("exit", () => {
 | 
				
			||||||
 | 
					                runproc = undefined;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					run();
 | 
				
			||||||
 | 
					chokidar.watch("src").on("change", run);
 | 
				
			||||||
 | 
					chokidar.watch("static/fixed_responses").on("change", run);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					chokidar.watch("static/webui").on("change", async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        await fetch("http://localhost/custom/webuiFileChangeDetected?secret=" + secret);
 | 
				
			||||||
 | 
					    } catch (e) {}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										46
									
								
								scripts/fix-imports.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								scripts/fix-imports.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					const fs = require("fs");
 | 
				
			||||||
 | 
					const path = require("path");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const root = path.join(process.cwd(), "..");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function listFiles(dir) {
 | 
				
			||||||
 | 
					    const entries = fs.readdirSync(dir, { withFileTypes: true });
 | 
				
			||||||
 | 
					    let results = [];
 | 
				
			||||||
 | 
					    for (const entry of entries) {
 | 
				
			||||||
 | 
					        const fullPath = path.join(dir, entry.name);
 | 
				
			||||||
 | 
					        if (entry.isDirectory()) {
 | 
				
			||||||
 | 
					            results = results.concat(listFiles(fullPath));
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            results.push(fullPath);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const files = listFiles(path.join(root, "src"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for (const file of files) {
 | 
				
			||||||
 | 
					    let content;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        content = fs.readFileSync(file, "utf8");
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const dir = path.dirname(file);
 | 
				
			||||||
 | 
					    const fixedContent = content.replaceAll(/} from "([^"]+)";/g, (sub, importPath) => {
 | 
				
			||||||
 | 
					        if (!importPath.startsWith("@/")) {
 | 
				
			||||||
 | 
					            const fullImportPath = path.resolve(dir, importPath);
 | 
				
			||||||
 | 
					            if (fs.existsSync(fullImportPath + ".ts")) {
 | 
				
			||||||
 | 
					                const relative = path.relative(root, fullImportPath).replace(/\\/g, "/");
 | 
				
			||||||
 | 
					                const fixedPath = "@/" + relative;
 | 
				
			||||||
 | 
					                console.log(`${importPath} -> ${fixedPath}`);
 | 
				
			||||||
 | 
					                return sub.split(importPath).join(fixedPath);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return sub;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (content != fixedContent) {
 | 
				
			||||||
 | 
					        fs.writeFileSync(file, fixedContent, "utf8");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
 | 
					// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
 | 
				
			||||||
// Converted via ChatGPT-4o
 | 
					// Converted via ChatGPT-4o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
const fs = require("fs");
 | 
					const fs = require("fs");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function extractStrings(content) {
 | 
					function extractStrings(content) {
 | 
				
			||||||
@ -30,7 +31,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
 | 
				
			|||||||
            const strings = extractStrings(line);
 | 
					            const strings = extractStrings(line);
 | 
				
			||||||
            if (Object.keys(strings).length > 0) {
 | 
					            if (Object.keys(strings).length > 0) {
 | 
				
			||||||
                Object.entries(strings).forEach(([key, value]) => {
 | 
					                Object.entries(strings).forEach(([key, value]) => {
 | 
				
			||||||
                    if (targetStrings.hasOwnProperty(key)) {
 | 
					                    if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED] ")) {
 | 
				
			||||||
                        fs.writeSync(fileHandle, `    ${key}: \`${targetStrings[key]}\`,\n`);
 | 
					                        fs.writeSync(fileHandle, `    ${key}: \`${targetStrings[key]}\`,\n`);
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        fs.writeSync(fileHandle, `    ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
 | 
					                        fs.writeSync(fileHandle, `    ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,6 @@ export const artifactsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (itemIndex !== -1) {
 | 
					    if (itemIndex !== -1) {
 | 
				
			||||||
        Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
 | 
					        Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
 | 
				
			||||||
        inventory.markModified(`Upgrades.${itemIndex}.UpgradeFingerprint`);
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        itemIndex =
 | 
					        itemIndex =
 | 
				
			||||||
            Upgrades.push({
 | 
					            Upgrades.push({
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,12 @@
 | 
				
			|||||||
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const checkDailyMissionBonusController: RequestHandler = (_req, res) => {
 | 
					export const checkDailyMissionBonusController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const data = Buffer.from([
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
        0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a,
 | 
					    const today = Math.trunc(Date.now() / 86400000) * 86400;
 | 
				
			||||||
        0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73,
 | 
					    if (account.DailyFirstWinDate != today) {
 | 
				
			||||||
        0x3a, 0x31, 0x0a
 | 
					        res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n");
 | 
				
			||||||
    ]);
 | 
					    } else {
 | 
				
			||||||
    res.writeHead(200, {
 | 
					        res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n");
 | 
				
			||||||
        "Content-Type": "text/html",
 | 
					    }
 | 
				
			||||||
        "Content-Length": data.length
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    res.end(data);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { checkDailyMissionBonusController };
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -13,15 +13,18 @@ import {
 | 
				
			|||||||
    addItem,
 | 
					    addItem,
 | 
				
			||||||
    addRecipes,
 | 
					    addRecipes,
 | 
				
			||||||
    occupySlot,
 | 
					    occupySlot,
 | 
				
			||||||
    combineInventoryChanges
 | 
					    combineInventoryChanges,
 | 
				
			||||||
 | 
					    addKubrowPetPrint,
 | 
				
			||||||
 | 
					    addPowerSuit,
 | 
				
			||||||
 | 
					    addEquipment
 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { InventorySlot, IPendingRecipeDatabase, Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					 | 
				
			||||||
import { toOid2 } from "@/src/helpers/inventoryHelpers";
 | 
					import { toOid2 } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import { IRecipe } from "warframe-public-export-plus";
 | 
					import { IRecipe } from "warframe-public-export-plus";
 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import { EquipmentFeatures, IEquipmentClient, Status } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IClaimCompletedRecipeRequest {
 | 
					interface IClaimCompletedRecipeRequest {
 | 
				
			||||||
    RecipeIds: IOid[];
 | 
					    RecipeIds: IOid[];
 | 
				
			||||||
@ -119,18 +122,126 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
 | 
					            pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
 | 
				
			||||||
 | 
					        } else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
 | 
				
			||||||
 | 
					            const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
 | 
				
			||||||
 | 
					            addKubrowPetPrint(inventory, pet, InventoryChanges);
 | 
				
			||||||
        } else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
 | 
					        } else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
 | 
				
			||||||
            InventoryChanges = {
 | 
					            if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
 | 
				
			||||||
                ...InventoryChanges,
 | 
					                // Quite the special case here...
 | 
				
			||||||
                ...(await addItem(
 | 
					                // We don't just get Umbra, but also Skiajati and Umbra Mods. Both items are max rank, potatoed, and with the mods are pre-installed.
 | 
				
			||||||
 | 
					                // Source: https://wiki.warframe.com/w/The_Sacrifice, https://wiki.warframe.com/w/Excalibur/Umbra, https://wiki.warframe.com/w/Skiajati
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const umbraModA = (
 | 
				
			||||||
 | 
					                    await addItem(
 | 
				
			||||||
 | 
					                        inventory,
 | 
				
			||||||
 | 
					                        "/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
 | 
				
			||||||
 | 
					                        1,
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        undefined,
 | 
				
			||||||
 | 
					                        `{"lvl":5}`
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                ).Upgrades![0];
 | 
				
			||||||
 | 
					                const umbraModB = (
 | 
				
			||||||
 | 
					                    await addItem(
 | 
				
			||||||
 | 
					                        inventory,
 | 
				
			||||||
 | 
					                        "/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
 | 
				
			||||||
 | 
					                        1,
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        undefined,
 | 
				
			||||||
 | 
					                        `{"lvl":5}`
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                ).Upgrades![0];
 | 
				
			||||||
 | 
					                const umbraModC = (
 | 
				
			||||||
 | 
					                    await addItem(
 | 
				
			||||||
 | 
					                        inventory,
 | 
				
			||||||
 | 
					                        "/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
 | 
				
			||||||
 | 
					                        1,
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        undefined,
 | 
				
			||||||
 | 
					                        `{"lvl":5}`
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                ).Upgrades![0];
 | 
				
			||||||
 | 
					                const sacrificeModA = (
 | 
				
			||||||
 | 
					                    await addItem(
 | 
				
			||||||
 | 
					                        inventory,
 | 
				
			||||||
 | 
					                        "/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
 | 
				
			||||||
 | 
					                        1,
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        undefined,
 | 
				
			||||||
 | 
					                        `{"lvl":5}`
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                ).Upgrades![0];
 | 
				
			||||||
 | 
					                const sacrificeModB = (
 | 
				
			||||||
 | 
					                    await addItem(
 | 
				
			||||||
 | 
					                        inventory,
 | 
				
			||||||
 | 
					                        "/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
 | 
				
			||||||
 | 
					                        1,
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        undefined,
 | 
				
			||||||
 | 
					                        `{"lvl":5}`
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                ).Upgrades![0];
 | 
				
			||||||
 | 
					                InventoryChanges.Upgrades ??= [];
 | 
				
			||||||
 | 
					                InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await addPowerSuit(
 | 
				
			||||||
                    inventory,
 | 
					                    inventory,
 | 
				
			||||||
                    recipe.resultType,
 | 
					                    "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
 | 
				
			||||||
                    recipe.num,
 | 
					                    {
 | 
				
			||||||
                    false,
 | 
					                        Configs: [
 | 
				
			||||||
                    undefined,
 | 
					                            {
 | 
				
			||||||
                    pendingRecipe.TargetFingerprint
 | 
					                                Upgrades: [
 | 
				
			||||||
                ))
 | 
					                                    "",
 | 
				
			||||||
            };
 | 
					                                    "",
 | 
				
			||||||
 | 
					                                    "",
 | 
				
			||||||
 | 
					                                    "",
 | 
				
			||||||
 | 
					                                    "",
 | 
				
			||||||
 | 
					                                    umbraModA.ItemId.$oid,
 | 
				
			||||||
 | 
					                                    umbraModB.ItemId.$oid,
 | 
				
			||||||
 | 
					                                    umbraModC.ItemId.$oid
 | 
				
			||||||
 | 
					                                ]
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        XP: 900_000,
 | 
				
			||||||
 | 
					                        Features: EquipmentFeatures.DOUBLE_CAPACITY
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    InventoryChanges
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                inventory.XPInfo.push({
 | 
				
			||||||
 | 
					                    ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
 | 
				
			||||||
 | 
					                    XP: 900_000
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                addEquipment(
 | 
				
			||||||
 | 
					                    inventory,
 | 
				
			||||||
 | 
					                    "Melee",
 | 
				
			||||||
 | 
					                    "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Configs: [
 | 
				
			||||||
 | 
					                            { Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        XP: 450_000,
 | 
				
			||||||
 | 
					                        Features: EquipmentFeatures.DOUBLE_CAPACITY
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    InventoryChanges
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                inventory.XPInfo.push({
 | 
				
			||||||
 | 
					                    ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
 | 
				
			||||||
 | 
					                    XP: 450_000
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                InventoryChanges = {
 | 
				
			||||||
 | 
					                    ...InventoryChanges,
 | 
				
			||||||
 | 
					                    ...(await addItem(
 | 
				
			||||||
 | 
					                        inventory,
 | 
				
			||||||
 | 
					                        recipe.resultType,
 | 
				
			||||||
 | 
					                        recipe.num,
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        undefined,
 | 
				
			||||||
 | 
					                        pendingRecipe.TargetFingerprint
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            config.claimingBlueprintRefundsIngredients &&
 | 
					            config.claimingBlueprintRefundsIngredients &&
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportChallenges } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const claimJunctionChallengeRewardController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					    const data = getJSONfromString<IClaimJunctionChallengeRewardRequest>(String(req.body));
 | 
				
			||||||
 | 
					    const challengeProgress = inventory.ChallengeProgress.find(x => x.Name == data.Challenge)!;
 | 
				
			||||||
 | 
					    if (challengeProgress.ReceivedJunctionReward) {
 | 
				
			||||||
 | 
					        throw new Error(`attempt to double-claim junction reward`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    challengeProgress.ReceivedJunctionReward = true;
 | 
				
			||||||
 | 
					    inventory.ClaimedJunctionChallengeRewards ??= [];
 | 
				
			||||||
 | 
					    inventory.ClaimedJunctionChallengeRewards.push(data.Challenge);
 | 
				
			||||||
 | 
					    const challengeMeta = Object.entries(ExportChallenges).find(arr => arr[0].endsWith("/" + data.Challenge))![1];
 | 
				
			||||||
 | 
					    const inventoryChanges = {};
 | 
				
			||||||
 | 
					    for (const reward of challengeMeta.countedRewards!) {
 | 
				
			||||||
 | 
					        combineInventoryChanges(
 | 
				
			||||||
 | 
					            inventoryChanges,
 | 
				
			||||||
 | 
					            (await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount)).InventoryChanges
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        inventoryChanges: inventoryChanges // Yeah, it's "inventoryChanges" in the response here.
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IClaimJunctionChallengeRewardRequest {
 | 
				
			||||||
 | 
					    Challenge: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { getCalendarProgress, getInventory } from "@/src/services/inventoryService";
 | 
					import { checkCalendarAutoAdvance, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
					import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
import { getWorldState } from "@/src/services/worldStateService";
 | 
					import { getWorldState } from "@/src/services/worldStateService";
 | 
				
			||||||
@ -12,27 +12,23 @@ export const completeCalendarEventController: RequestHandler = async (req, res)
 | 
				
			|||||||
    const calendarProgress = getCalendarProgress(inventory);
 | 
					    const calendarProgress = getCalendarProgress(inventory);
 | 
				
			||||||
    const currentSeason = getWorldState().KnownCalendarSeasons[0];
 | 
					    const currentSeason = getWorldState().KnownCalendarSeasons[0];
 | 
				
			||||||
    let inventoryChanges: IInventoryChanges = {};
 | 
					    let inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
    let dayIndex = 0;
 | 
					    const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
 | 
				
			||||||
    for (const day of currentSeason.Days) {
 | 
					    const day = currentSeason.Days[dayIndex];
 | 
				
			||||||
        if (day.events.length == 0 || day.events[0].type != "CET_CHALLENGE") {
 | 
					    if (day.events.length != 0) {
 | 
				
			||||||
            if (dayIndex == calendarProgress.SeasonProgress.LastCompletedDayIdx) {
 | 
					        if (day.events[0].type == "CET_CHALLENGE") {
 | 
				
			||||||
                if (day.events.length != 0) {
 | 
					            throw new Error(`completeCalendarEvent should not be used for challenges`);
 | 
				
			||||||
                    const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
 | 
					        }
 | 
				
			||||||
                    if (selection.type == "CET_REWARD") {
 | 
					        const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
 | 
				
			||||||
                        inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory))
 | 
					        if (selection.type == "CET_REWARD") {
 | 
				
			||||||
                            .InventoryChanges;
 | 
					            inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory)).InventoryChanges;
 | 
				
			||||||
                    } else if (selection.type == "CET_UPGRADE") {
 | 
					        } else if (selection.type == "CET_UPGRADE") {
 | 
				
			||||||
                        calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
 | 
					            calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
 | 
				
			||||||
                    } else if (selection.type != "CET_PLOT") {
 | 
					        } else if (selection.type != "CET_PLOT") {
 | 
				
			||||||
                        throw new Error(`unexpected selection type: ${selection.type}`);
 | 
					            throw new Error(`unexpected selection type: ${selection.type}`);
 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            ++dayIndex;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    calendarProgress.SeasonProgress.LastCompletedDayIdx++;
 | 
					    calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex;
 | 
				
			||||||
 | 
					    checkCalendarAutoAdvance(inventory, currentSeason);
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        InventoryChanges: inventoryChanges,
 | 
					        InventoryChanges: inventoryChanges,
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,8 @@ import {
 | 
				
			|||||||
    updateCurrency
 | 
					    updateCurrency
 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					} 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 { ITypeCount } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					import { IFusionTreasure, IMiscItem } 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) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -4,9 +4,15 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			|||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const creditsController: RequestHandler = async (req, res) => {
 | 
					export const creditsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const inventory = (
 | 
				
			||||||
 | 
					        await Promise.all([
 | 
				
			||||||
    const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
 | 
					            getAccountIdForRequest(req),
 | 
				
			||||||
 | 
					            getInventory(
 | 
				
			||||||
 | 
					                req.query.accountId as string,
 | 
				
			||||||
 | 
					                "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					    )[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const response = {
 | 
					    const response = {
 | 
				
			||||||
        RegularCredits: inventory.RegularCredits,
 | 
					        RegularCredits: inventory.RegularCredits,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										107
									
								
								src/controllers/api/crewShipFusionController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/controllers/api/crewShipFusionController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { addMiscItems, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					import { ICrewShipComponentFingerprint, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const crewShipFusionController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<ICrewShipFusionRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isWeapon = inventory.CrewShipWeapons.id(payload.PartA.$oid);
 | 
				
			||||||
 | 
					    const itemA = isWeapon ?? inventory.CrewShipWeaponSkins.id(payload.PartA.$oid)!;
 | 
				
			||||||
 | 
					    const category = isWeapon ? "CrewShipWeapons" : "CrewShipWeaponSkins";
 | 
				
			||||||
 | 
					    const salvageCategory = isWeapon ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
 | 
				
			||||||
 | 
					    const itemB = inventory[payload.SourceRecipe ? salvageCategory : category].id(payload.PartB.$oid)!;
 | 
				
			||||||
 | 
					    const tierA = itemA.ItemType.charCodeAt(itemA.ItemType.length - 1) - 65;
 | 
				
			||||||
 | 
					    const tierB = itemB.ItemType.charCodeAt(itemB.ItemType.length - 1) - 65;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Charge partial repair cost if fusing with an identified but unrepaired part
 | 
				
			||||||
 | 
					    if (payload.SourceRecipe) {
 | 
				
			||||||
 | 
					        const recipe = ExportDojoRecipes.research[payload.SourceRecipe];
 | 
				
			||||||
 | 
					        updateCurrency(inventory, Math.round(recipe.price * 0.4), false, inventoryChanges);
 | 
				
			||||||
 | 
					        const miscItemChanges = recipe.ingredients.map(x => ({ ...x, ItemCount: Math.round(x.ItemCount * -0.4) }));
 | 
				
			||||||
 | 
					        addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
 | 
					        inventoryChanges.MiscItems = miscItemChanges;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove inferior item
 | 
				
			||||||
 | 
					    if (payload.SourceRecipe) {
 | 
				
			||||||
 | 
					        inventory[salvageCategory].pull({ _id: payload.PartB.$oid });
 | 
				
			||||||
 | 
					        inventoryChanges.RemovedIdItems = [{ ItemId: payload.PartB }];
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const inferiorId = tierA < tierB ? payload.PartA : payload.PartB;
 | 
				
			||||||
 | 
					        inventory[category].pull({ _id: inferiorId.$oid });
 | 
				
			||||||
 | 
					        inventoryChanges.RemovedIdItems = [{ ItemId: inferiorId }];
 | 
				
			||||||
 | 
					        freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
 | 
				
			||||||
 | 
					        inventoryChanges[InventorySlot.RJ_COMPONENT_AND_ARMAMENTS] = { count: -1, platinum: 0, Slots: 1 };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Upgrade superior item
 | 
				
			||||||
 | 
					    const superiorItem = tierA < tierB ? itemB : itemA;
 | 
				
			||||||
 | 
					    const inferiorItem = tierA < tierB ? itemA : itemB;
 | 
				
			||||||
 | 
					    const fingerprint: ICrewShipComponentFingerprint = JSON.parse(
 | 
				
			||||||
 | 
					        superiorItem.UpgradeFingerprint!
 | 
				
			||||||
 | 
					    ) as ICrewShipComponentFingerprint;
 | 
				
			||||||
 | 
					    const inferiorFingerprint: ICrewShipComponentFingerprint = inferiorItem.UpgradeFingerprint
 | 
				
			||||||
 | 
					        ? (JSON.parse(inferiorItem.UpgradeFingerprint) as ICrewShipComponentFingerprint)
 | 
				
			||||||
 | 
					        : { compat: "", buffs: [] };
 | 
				
			||||||
 | 
					    if (isWeapon) {
 | 
				
			||||||
 | 
					        for (let i = 0; i != fingerprint.buffs.length; ++i) {
 | 
				
			||||||
 | 
					            const buffA = fingerprint.buffs[i];
 | 
				
			||||||
 | 
					            const buffB = i < inferiorFingerprint.buffs.length ? inferiorFingerprint.buffs[i] : undefined;
 | 
				
			||||||
 | 
					            const fvalA = buffA.Value / 0x3fffffff;
 | 
				
			||||||
 | 
					            const fvalB = (buffB?.Value ?? 0) / 0x3fffffff;
 | 
				
			||||||
 | 
					            const percA = 0.3 + fvalA * (0.6 - 0.3);
 | 
				
			||||||
 | 
					            const percB = 0.3 + fvalB * (0.6 - 0.3);
 | 
				
			||||||
 | 
					            const newPerc = Math.min(0.6, Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]);
 | 
				
			||||||
 | 
					            const newFval = (newPerc - 0.3) / (0.6 - 0.3);
 | 
				
			||||||
 | 
					            buffA.Value = Math.trunc(newFval * 0x3fffffff);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const superiorMeta = ExportCustoms[superiorItem.ItemType].randomisedUpgrades ?? [];
 | 
				
			||||||
 | 
					        const inferiorMeta = ExportCustoms[inferiorItem.ItemType].randomisedUpgrades ?? [];
 | 
				
			||||||
 | 
					        for (let i = 0; i != inferiorFingerprint.buffs.length; ++i) {
 | 
				
			||||||
 | 
					            const buffA = fingerprint.buffs[i];
 | 
				
			||||||
 | 
					            const buffB = inferiorFingerprint.buffs[i];
 | 
				
			||||||
 | 
					            const fvalA = buffA.Value / 0x3fffffff;
 | 
				
			||||||
 | 
					            const fvalB = buffB.Value / 0x3fffffff;
 | 
				
			||||||
 | 
					            const rangeA = superiorMeta[i].range;
 | 
				
			||||||
 | 
					            const rangeB = inferiorMeta[i].range;
 | 
				
			||||||
 | 
					            const percA = rangeA[0] + fvalA * (rangeA[1] - rangeA[0]);
 | 
				
			||||||
 | 
					            const percB = rangeB[0] + fvalB * (rangeB[1] - rangeB[0]);
 | 
				
			||||||
 | 
					            const newPerc = Math.min(rangeA[1], Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]);
 | 
				
			||||||
 | 
					            const newFval = (newPerc - rangeA[0]) / (rangeA[1] - rangeA[0]);
 | 
				
			||||||
 | 
					            buffA.Value = Math.trunc(newFval * 0x3fffffff);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (inferiorFingerprint.SubroutineIndex) {
 | 
				
			||||||
 | 
					            const useSuperiorSubroutine = tierA < tierB ? !payload.UseSubroutineA : payload.UseSubroutineA;
 | 
				
			||||||
 | 
					            if (!useSuperiorSubroutine) {
 | 
				
			||||||
 | 
					                fingerprint.SubroutineIndex = inferiorFingerprint.SubroutineIndex;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint);
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					    inventoryChanges[category] = [superiorItem.toJSON() as any];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ICrewShipFusionRequest {
 | 
				
			||||||
 | 
					    PartA: IOid;
 | 
				
			||||||
 | 
					    PartB: IOid;
 | 
				
			||||||
 | 
					    SourceRecipe: string;
 | 
				
			||||||
 | 
					    UseSubroutineA: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FUSE_MULTIPLIERS = [1.1, 1.05, 1.02];
 | 
				
			||||||
@ -12,7 +12,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { getRandomInt } from "@/src/services/rngService";
 | 
					import { getRandomInt } from "@/src/services/rngService";
 | 
				
			||||||
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
 | 
					import { IFingerprintStat } from "@/src/helpers/rivenHelper";
 | 
				
			||||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IEquipmentDatabase } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
 | 
					export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,15 @@
 | 
				
			|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { Guild } from "@/src/models/guildModel";
 | 
					import { Guild } from "@/src/models/guildModel";
 | 
				
			||||||
import { getAccountForRequest } from "@/src/services/loginService";
 | 
					import { hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountForRequest, getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { GuildPermission } from "@/src/types/guildTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
 | 
					export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
 | 
					    const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
 | 
				
			||||||
    const guild = (await Guild.findById(data.g, "DojoComponents"))!;
 | 
					    const guild = (await Guild.findById(data.g, "DojoComponents Ranks"))!;
 | 
				
			||||||
    const component = guild.DojoComponents.id(data.c)!;
 | 
					    const component = guild.DojoComponents.id(data.c)!;
 | 
				
			||||||
    if (req.query.act == "f") {
 | 
					    if (req.query.act == "f") {
 | 
				
			||||||
        res.json({
 | 
					        res.json({
 | 
				
			||||||
@ -34,6 +37,19 @@ export const customObstacleCourseLeaderboardController: RequestHandler = async (
 | 
				
			|||||||
            entry.r = ++r;
 | 
					            entry.r = ++r;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        await guild.save();
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					        res.status(200).end();
 | 
				
			||||||
 | 
					    } else if (req.query.act == "c") {
 | 
				
			||||||
 | 
					        // TOVERIFY: What clan permission is actually needed for this?
 | 
				
			||||||
 | 
					        const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					        const inventory = await getInventory(accountId, "GuildId LevelKeys");
 | 
				
			||||||
 | 
					        if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
 | 
				
			||||||
 | 
					            res.status(400).end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        component.Leaderboard = undefined;
 | 
				
			||||||
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res.status(200).end();
 | 
					        res.status(200).end();
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
					        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			|||||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
 | 
					import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
 | 
				
			||||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { EquipmentFeatures } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const evolveWeaponController: RequestHandler = async (req, res) => {
 | 
					export const evolveWeaponController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
				
			|||||||
@ -30,15 +30,14 @@ 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);
 | 
				
			||||||
    let affiliationMod;
 | 
					    if (gainedStanding && syndicateTag) addStanding(inventory, syndicateTag, gainedStanding);
 | 
				
			||||||
    if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
 | 
					 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        InventoryChanges: {
 | 
					        InventoryChanges: {
 | 
				
			||||||
            MiscItems: miscItemChanges
 | 
					            MiscItems: miscItemChanges
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        SyndicateTag: syndicateTag,
 | 
					        SyndicateTag: syndicateTag,
 | 
				
			||||||
        StandingChange: affiliationMod?.Standing || 0
 | 
					        StandingChange: gainedStanding
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/serv
 | 
				
			|||||||
import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { ExportFocusUpgrades } from "warframe-public-export-plus";
 | 
					import { ExportFocusUpgrades } from "warframe-public-export-plus";
 | 
				
			||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					 | 
				
			||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const focusController: RequestHandler = async (req, res) => {
 | 
					export const focusController: RequestHandler = async (req, res) => {
 | 
				
			||||||
@ -43,7 +42,7 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            inventory.FocusAbility ??= focusType;
 | 
					            inventory.FocusAbility ??= focusType;
 | 
				
			||||||
            inventory.FocusUpgrades.push({ ItemType: focusType });
 | 
					            inventory.FocusUpgrades.push({ ItemType: focusType });
 | 
				
			||||||
            if (inventory.FocusXP) {
 | 
					            if (inventory.FocusXP) {
 | 
				
			||||||
                inventory.FocusXP[focusPolarity] -= cost;
 | 
					                inventory.FocusXP[focusPolarity]! -= cost;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json({
 | 
					            res.json({
 | 
				
			||||||
@ -78,7 +77,7 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
 | 
					                cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
 | 
				
			||||||
                inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
 | 
					                inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            inventory.FocusXP![focusPolarity] -= cost;
 | 
					            inventory.FocusXP![focusPolarity]! -= cost;
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json({
 | 
					            res.json({
 | 
				
			||||||
                FocusTypes: request.FocusTypes,
 | 
					                FocusTypes: request.FocusTypes,
 | 
				
			||||||
@ -96,7 +95,7 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
 | 
					                const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
 | 
				
			||||||
                focusUpgradeDb.Level = focusUpgrade.Level;
 | 
					                focusUpgradeDb.Level = focusUpgrade.Level;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            inventory.FocusXP![focusPolarity] -= cost;
 | 
					            inventory.FocusXP![focusPolarity]! -= cost;
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json({
 | 
					            res.json({
 | 
				
			||||||
                FocusInfos: request.FocusInfos,
 | 
					                FocusInfos: request.FocusInfos,
 | 
				
			||||||
@ -116,14 +115,14 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            });
 | 
					            });
 | 
				
			||||||
            occupySlot(inventory, InventorySlot.AMPS, false);
 | 
					            occupySlot(inventory, InventorySlot.AMPS, false);
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
 | 
					            res.json(inventoryChanges.OperatorAmps![0]);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case FocusOperation.UnbindUpgrade: {
 | 
					        case FocusOperation.UnbindUpgrade: {
 | 
				
			||||||
            const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
 | 
					            const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
 | 
				
			||||||
            const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
 | 
					            const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
 | 
				
			||||||
            const inventory = await getInventory(accountId);
 | 
					            const inventory = await getInventory(accountId);
 | 
				
			||||||
            inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length;
 | 
					            inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length;
 | 
				
			||||||
            addMiscItems(inventory, [
 | 
					            addMiscItems(inventory, [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
 | 
					                    ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
 | 
				
			||||||
@ -168,8 +167,10 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                shard.ItemCount *= -1;
 | 
					                shard.ItemCount *= -1;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const inventory = await getInventory(accountId);
 | 
					            const inventory = await getInventory(accountId);
 | 
				
			||||||
            inventory.FocusXP ??= { AP_POWER: 0, AP_TACTIC: 0, AP_DEFENSE: 0, AP_ATTACK: 0, AP_WARD: 0 };
 | 
					            const polarity = request.Polarity;
 | 
				
			||||||
            inventory.FocusXP[request.Polarity] += xp;
 | 
					            inventory.FocusXP ??= {};
 | 
				
			||||||
 | 
					            inventory.FocusXP[polarity] ??= 0;
 | 
				
			||||||
 | 
					            inventory.FocusXP[polarity] += xp;
 | 
				
			||||||
            addMiscItems(inventory, request.Shards);
 | 
					            addMiscItems(inventory, request.Shards);
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
				
			|||||||
@ -6,9 +6,8 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			|||||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
					import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
				
			||||||
import { IMongoDate } from "@/src/types/commonTypes";
 | 
					import { IMongoDate } from "@/src/types/commonTypes";
 | 
				
			||||||
import { IMissionReward } from "@/src/types/missionTypes";
 | 
					import { IMissionReward } from "@/src/types/missionTypes";
 | 
				
			||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
 | 
					import { IGardeningClient, IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { IGardeningClient } from "@/src/types/shipTypes";
 | 
					 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { dict_en, ExportResources } from "warframe-public-export-plus";
 | 
					import { dict_en, ExportResources } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,10 @@
 | 
				
			|||||||
 | 
					import { DailyDeal } from "@/src/models/worldStateModel";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getDailyDealStockLevelsController: RequestHandler = (req, res) => {
 | 
					export const getDailyDealStockLevelsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const dailyDeal = (await DailyDeal.findOne({ StoreItem: req.query.productName }, "AmountSold"))!;
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        StoreItem: req.query.productName,
 | 
					        StoreItem: req.query.productName,
 | 
				
			||||||
        AmountSold: 0
 | 
					        AmountSold: dailyDeal.AmountSold
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
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 { generateRewardSeed } from "@/src/services/rngService";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
 | 
					export const getNewRewardSeedController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,10 +3,9 @@ import { config } from "@/src/services/configService";
 | 
				
			|||||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
 | 
					import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
					import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
				
			||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
					import { IGetShipResponse, IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
 | 
				
			||||||
import { IGetShipResponse } from "@/src/types/shipTypes";
 | 
					 | 
				
			||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
 | 
					 | 
				
			||||||
import { getLoadout } from "@/src/services/loadoutService";
 | 
					import { getLoadout } from "@/src/services/loadoutService";
 | 
				
			||||||
 | 
					import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getShipController: RequestHandler = async (req, res) => {
 | 
					export const getShipController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -26,15 +25,7 @@ export const getShipController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
 | 
					        LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
 | 
				
			||||||
        Ship: {
 | 
					        Ship: {
 | 
				
			||||||
            ...personalRooms.Ship,
 | 
					            ...personalRooms.Ship,
 | 
				
			||||||
            ShipId: toOid(personalRoomsDb.activeShipId),
 | 
					            ShipId: toOid(personalRoomsDb.activeShipId)
 | 
				
			||||||
            ShipInterior: {
 | 
					 | 
				
			||||||
                Colors: personalRooms.ShipInteriorColors,
 | 
					 | 
				
			||||||
                ShipAttachments: { HOOD_ORNAMENT: "" },
 | 
					 | 
				
			||||||
                SkinFlavourItem: ""
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
 | 
					 | 
				
			||||||
                ? toOid(personalRooms.Ship.FavouriteLoadoutId)
 | 
					 | 
				
			||||||
                : undefined
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        Apartment: personalRooms.Apartment,
 | 
					        Apartment: personalRooms.Apartment,
 | 
				
			||||||
        TailorShop: personalRooms.TailorShop
 | 
					        TailorShop: personalRooms.TailorShop
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import { RequestHandler } from "express";
 | 
				
			|||||||
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
 | 
					import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
 | 
				
			||||||
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 { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getVendorInfoController: RequestHandler = async (req, res) => {
 | 
					export const getVendorInfoController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    let manifest = getVendorManifestByTypeName(req.query.vendor as string);
 | 
					    let manifest = getVendorManifestByTypeName(req.query.vendor as string);
 | 
				
			||||||
@ -14,6 +15,14 @@ export const getVendorInfoController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        const accountId = await getAccountIdForRequest(req);
 | 
					        const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
        const inventory = await getInventory(accountId);
 | 
					        const inventory = await getInventory(accountId);
 | 
				
			||||||
        manifest = applyStandingToVendorManifest(inventory, manifest);
 | 
					        manifest = applyStandingToVendorManifest(inventory, manifest);
 | 
				
			||||||
 | 
					        if (config.dev?.keepVendorsExpired) {
 | 
				
			||||||
 | 
					            manifest = {
 | 
				
			||||||
 | 
					                VendorInfo: {
 | 
				
			||||||
 | 
					                    ...manifest.VendorInfo,
 | 
				
			||||||
 | 
					                    Expiry: { $date: { $numberLong: "0" } }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json(manifest);
 | 
					    res.json(manifest);
 | 
				
			||||||
 | 
				
			|||||||
@ -9,15 +9,26 @@ import {
 | 
				
			|||||||
    updateCurrency
 | 
					    updateCurrency
 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
					import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
					import { handleDailyDealPurchase, handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
import { IOid } from "@/src/types/commonTypes";
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { IInventoryChanges, IPurchaseParams } from "@/src/types/purchaseTypes";
 | 
					import { IPurchaseParams, IPurchaseResponse, PurchaseSource } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
 | 
					import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkPurchaseParams = (params: IPurchaseParams): boolean => {
 | 
				
			||||||
 | 
					    switch (params.Source) {
 | 
				
			||||||
 | 
					        case PurchaseSource.Market:
 | 
				
			||||||
 | 
					            return params.UsePremium;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case PurchaseSource.DailyDeal:
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const giftingController: RequestHandler = async (req, res) => {
 | 
					export const giftingController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const data = getJSONfromString<IGiftingRequest>(String(req.body));
 | 
					    const data = getJSONfromString<IGiftingRequest>(String(req.body));
 | 
				
			||||||
    if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
 | 
					    if (!checkPurchaseParams(data.PurchaseParams)) {
 | 
				
			||||||
        throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
 | 
					        throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -58,16 +69,19 @@ export const giftingController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    senderInventory.GiftsRemaining -= 1;
 | 
					    senderInventory.GiftsRemaining -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventoryChanges: IInventoryChanges = updateCurrency(
 | 
					    const response: IPurchaseResponse = {
 | 
				
			||||||
        senderInventory,
 | 
					        InventoryChanges: {}
 | 
				
			||||||
        data.PurchaseParams.ExpectedPrice,
 | 
					    };
 | 
				
			||||||
        true
 | 
					    if (data.PurchaseParams.Source == PurchaseSource.DailyDeal) {
 | 
				
			||||||
    );
 | 
					        await handleDailyDealPurchase(senderInventory, data.PurchaseParams, response);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true, response.InventoryChanges);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (data.PurchaseParams.StoreItem in ExportBundles) {
 | 
					    if (data.PurchaseParams.StoreItem in ExportBundles) {
 | 
				
			||||||
        const bundle = ExportBundles[data.PurchaseParams.StoreItem];
 | 
					        const bundle = ExportBundles[data.PurchaseParams.StoreItem];
 | 
				
			||||||
        if (bundle.giftingBonus) {
 | 
					        if (bundle.giftingBonus) {
 | 
				
			||||||
            combineInventoryChanges(
 | 
					            combineInventoryChanges(
 | 
				
			||||||
                inventoryChanges,
 | 
					                response.InventoryChanges,
 | 
				
			||||||
                (await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
 | 
					                (await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -99,9 +113,7 @@ export const giftingController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({
 | 
					    res.json(response);
 | 
				
			||||||
        InventoryChanges: inventoryChanges
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGiftingRequest {
 | 
					interface IGiftingRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,10 @@ 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 { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { ArtifactPolarity } 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";
 | 
				
			||||||
 | 
					import { EquipmentFeatures, IEquipmentClient } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGildWeaponRequest {
 | 
					interface IGildWeaponRequest {
 | 
				
			||||||
    ItemName: string;
 | 
					    ItemName: string;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,8 @@
 | 
				
			|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
 | 
					import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { ITypeCount } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					import { ILoreFragmentScan } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
 | 
					export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -5,13 +5,14 @@ import {
 | 
				
			|||||||
    getGuildVault,
 | 
					    getGuildVault,
 | 
				
			||||||
    hasAccessToDojo,
 | 
					    hasAccessToDojo,
 | 
				
			||||||
    hasGuildPermission,
 | 
					    hasGuildPermission,
 | 
				
			||||||
 | 
					    processCompletedGuildTechProject,
 | 
				
			||||||
    processFundedGuildTechProject,
 | 
					    processFundedGuildTechProject,
 | 
				
			||||||
    processGuildTechProjectContributionsUpdate,
 | 
					    processGuildTechProjectContributionsUpdate,
 | 
				
			||||||
    removePigmentsFromGuildMembers,
 | 
					    removePigmentsFromGuildMembers,
 | 
				
			||||||
    scaleRequiredCount,
 | 
					    scaleRequiredCount,
 | 
				
			||||||
    setGuildTechLogState
 | 
					    setGuildTechLogState
 | 
				
			||||||
} from "@/src/services/guildService";
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes, ExportRailjackWeapons } from "warframe-public-export-plus";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    addCrewShipWeaponSkin,
 | 
					    addCrewShipWeaponSkin,
 | 
				
			||||||
@ -51,8 +52,12 @@ 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 (
 | 
				
			||||||
                        needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
 | 
					                        Date.now() >= project.CompletionDate.getTime() &&
 | 
				
			||||||
 | 
					                        setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate)
 | 
				
			||||||
 | 
					                    ) {
 | 
				
			||||||
 | 
					                        processCompletedGuildTechProject(guild, project.ItemType);
 | 
				
			||||||
 | 
					                        needSave = true;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                techProjects.push(techProject);
 | 
					                techProjects.push(techProject);
 | 
				
			||||||
@ -442,6 +447,7 @@ const finishComponentRepair = (
 | 
				
			|||||||
        ...(category == "CrewShipWeaponSkins"
 | 
					        ...(category == "CrewShipWeaponSkins"
 | 
				
			||||||
            ? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
 | 
					            ? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
 | 
				
			||||||
            : addEquipment(inventory, category, salvageItem.ItemType, {
 | 
					            : addEquipment(inventory, category, salvageItem.ItemType, {
 | 
				
			||||||
 | 
					                  UpgradeType: ExportRailjackWeapons[salvageItem.ItemType].defaultUpgrades?.[0].ItemType,
 | 
				
			||||||
                  UpgradeFingerprint: salvageItem.UpgradeFingerprint
 | 
					                  UpgradeFingerprint: salvageItem.UpgradeFingerprint
 | 
				
			||||||
              })),
 | 
					              })),
 | 
				
			||||||
        ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
 | 
					        ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
 | 
				
			||||||
 | 
				
			|||||||
@ -30,8 +30,9 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            const request = getJSONfromString<IShardInstallRequest>(String(req.body));
 | 
					            const request = getJSONfromString<IShardInstallRequest>(String(req.body));
 | 
				
			||||||
            const inventory = await getInventory(account._id.toString());
 | 
					            const inventory = await getInventory(account._id.toString());
 | 
				
			||||||
            const suit = inventory.Suits.id(request.SuitId.$oid)!;
 | 
					            const suit = inventory.Suits.id(request.SuitId.$oid)!;
 | 
				
			||||||
            if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
 | 
					            suit.ArchonCrystalUpgrades ??= [];
 | 
				
			||||||
                suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
 | 
					            while (suit.ArchonCrystalUpgrades.length < request.Slot) {
 | 
				
			||||||
 | 
					                suit.ArchonCrystalUpgrades.push({});
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            suit.ArchonCrystalUpgrades[request.Slot] = {
 | 
					            suit.ArchonCrystalUpgrades[request.Slot] = {
 | 
				
			||||||
                UpgradeType: request.UpgradeType,
 | 
					                UpgradeType: request.UpgradeType,
 | 
				
			||||||
@ -92,7 +93,8 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // remove from suit
 | 
					            // remove from suit
 | 
				
			||||||
            suit.ArchonCrystalUpgrades![request.Slot] = {};
 | 
					            suit.ArchonCrystalUpgrades![request.Slot].UpgradeType = undefined;
 | 
				
			||||||
 | 
					            suit.ArchonCrystalUpgrades![request.Slot].Color = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -5,30 +5,32 @@ import { config } from "@/src/services/configService";
 | 
				
			|||||||
import allDialogue from "@/static/fixed_responses/allDialogue.json";
 | 
					import allDialogue from "@/static/fixed_responses/allDialogue.json";
 | 
				
			||||||
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
 | 
					import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
 | 
				
			||||||
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IPolarity, ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import {
 | 
					import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
 | 
				
			||||||
    ExportCustoms,
 | 
					 | 
				
			||||||
    ExportFlavour,
 | 
					 | 
				
			||||||
    ExportRegions,
 | 
					 | 
				
			||||||
    ExportResources,
 | 
					 | 
				
			||||||
    ExportVirtuals
 | 
					 | 
				
			||||||
} from "warframe-public-export-plus";
 | 
					 | 
				
			||||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
 | 
					import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    addEmailItem,
 | 
				
			||||||
    addMiscItems,
 | 
					    addMiscItems,
 | 
				
			||||||
    allDailyAffiliationKeys,
 | 
					    allDailyAffiliationKeys,
 | 
				
			||||||
 | 
					    checkCalendarAutoAdvance,
 | 
				
			||||||
    cleanupInventory,
 | 
					    cleanupInventory,
 | 
				
			||||||
    createLibraryDailyTask,
 | 
					    createLibraryDailyTask,
 | 
				
			||||||
    generateRewardSeed
 | 
					    getCalendarProgress
 | 
				
			||||||
} from "@/src/services/inventoryService";
 | 
					} from "@/src/services/inventoryService";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
 | 
					import { addString, catBreadHash } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
 | 
					import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
 | 
				
			||||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
					import { getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
				
			||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
 | 
					import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
 | 
				
			||||||
import { Ship } from "@/src/models/shipModel";
 | 
					import { Ship } from "@/src/models/shipModel";
 | 
				
			||||||
import { toLegacyOid, version_compare } from "@/src/helpers/inventoryHelpers";
 | 
					import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { Inbox } from "@/src/models/inboxModel";
 | 
				
			||||||
 | 
					import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
				
			||||||
 | 
					import { DailyDeal } from "@/src/models/worldStateModel";
 | 
				
			||||||
 | 
					import { EquipmentFeatures } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					import { generateRewardSeed } from "@/src/services/rngService";
 | 
				
			||||||
 | 
					import { getWorldState } from "@/src/services/worldStateService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const inventoryController: RequestHandler = async (request, response) => {
 | 
					export const inventoryController: RequestHandler = async (request, response) => {
 | 
				
			||||||
    const account = await getAccountForRequest(request);
 | 
					    const account = await getAccountForRequest(request);
 | 
				
			||||||
@ -42,6 +44,8 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Handle daily reset
 | 
					    // Handle daily reset
 | 
				
			||||||
    if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
 | 
					    if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
 | 
				
			||||||
 | 
					        const today = Math.trunc(Date.now() / 86400000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const key of allDailyAffiliationKeys) {
 | 
					        for (const key of allDailyAffiliationKeys) {
 | 
				
			||||||
            inventory[key] = 16000 + inventory.PlayerLevel * 500;
 | 
					            inventory[key] = 16000 + inventory.PlayerLevel * 500;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -52,12 +56,12 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
				
			|||||||
        inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
 | 
					        inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (inventory.NextRefill) {
 | 
					        if (inventory.NextRefill) {
 | 
				
			||||||
 | 
					            const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
 | 
				
			||||||
 | 
					            const daysPassed = today - lastLoginDay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (config.noArgonCrystalDecay) {
 | 
					            if (config.noArgonCrystalDecay) {
 | 
				
			||||||
                inventory.FoundToday = undefined;
 | 
					                inventory.FoundToday = undefined;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
 | 
					 | 
				
			||||||
                const today = Math.trunc(Date.now() / 86400000);
 | 
					 | 
				
			||||||
                const daysPassed = today - lastLoginDay;
 | 
					 | 
				
			||||||
                for (let i = 0; i != daysPassed; ++i) {
 | 
					                for (let i = 0; i != daysPassed; ++i) {
 | 
				
			||||||
                    const numArgonCrystals =
 | 
					                    const numArgonCrystals =
 | 
				
			||||||
                        inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
 | 
					                        inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
 | 
				
			||||||
@ -89,11 +93,87 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
				
			|||||||
                    inventory.FoundToday = undefined;
 | 
					                    inventory.FoundToday = undefined;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (inventory.UsedDailyDeals.length != 0) {
 | 
				
			||||||
 | 
					                if (daysPassed == 1) {
 | 
				
			||||||
 | 
					                    const todayAt0Utc = today * 86400000;
 | 
				
			||||||
 | 
					                    const darvoIndex = Math.trunc((todayAt0Utc - 25200000) / (26 * unixTimesInMs.hour));
 | 
				
			||||||
 | 
					                    const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000;
 | 
				
			||||||
 | 
					                    const darvoOid =
 | 
				
			||||||
 | 
					                        ((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95";
 | 
				
			||||||
 | 
					                    const deal = await DailyDeal.findById(darvoOid);
 | 
				
			||||||
 | 
					                    if (deal) {
 | 
				
			||||||
 | 
					                        inventory.UsedDailyDeals = inventory.UsedDailyDeals.filter(x => x == deal.StoreItem); // keep only the deal that came into this new day with us
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        inventory.UsedDailyDeals = [];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    inventory.UsedDailyDeals = [];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TODO: Setup CalendarProgress as part of 1999 mission completion?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const previousYearIteration = inventory.CalendarProgress?.Iteration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // We need to do the following to ensure the in-game calendar does not break:
 | 
				
			||||||
 | 
					        getCalendarProgress(inventory); // Keep the CalendarProgress up-to-date (at least for the current year iteration) (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2364)
 | 
				
			||||||
 | 
					        checkCalendarAutoAdvance(inventory, getWorldState().KnownCalendarSeasons[0]); // Skip birthday events for characters if we do not have them unlocked yet (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2424)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // also handle sending of kiss cinematic at year rollover
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            inventory.CalendarProgress!.Iteration != previousYearIteration &&
 | 
				
			||||||
 | 
					            inventory.DialogueHistory &&
 | 
				
			||||||
 | 
					            inventory.DialogueHistory.Dialogues
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            let kalymos = false;
 | 
				
			||||||
 | 
					            for (const { dialogueName, kissEmail } of [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
 | 
				
			||||||
 | 
					                    kissEmail: "/Lotus/Types/Items/EmailItems/ArthurKissEmailItem"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
 | 
				
			||||||
 | 
					                    kissEmail: "/Lotus/Types/Items/EmailItems/EleanorKissEmailItem"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
 | 
				
			||||||
 | 
					                    kissEmail: "/Lotus/Types/Items/EmailItems/LettieKissEmailItem"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
 | 
				
			||||||
 | 
					                    kissEmail: "/Lotus/Types/Items/EmailItems/AmirKissEmailItem"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
 | 
				
			||||||
 | 
					                    kissEmail: "/Lotus/Types/Items/EmailItems/AoiKissEmailItem"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue",
 | 
				
			||||||
 | 
					                    kissEmail: "/Lotus/Types/Items/EmailItems/QuincyKissEmailItem"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]) {
 | 
				
			||||||
 | 
					                const dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == dialogueName);
 | 
				
			||||||
 | 
					                if (dialogue) {
 | 
				
			||||||
 | 
					                    if (dialogue.Rank == 7) {
 | 
				
			||||||
 | 
					                        await addEmailItem(inventory, kissEmail);
 | 
				
			||||||
 | 
					                        kalymos = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (dialogue.Rank == 6) {
 | 
				
			||||||
 | 
					                        kalymos = true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (kalymos) {
 | 
				
			||||||
 | 
					                await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cleanupInventory(inventory);
 | 
					        cleanupInventory(inventory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
 | 
					        inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
 | 
				
			||||||
        //await inventory.save();
 | 
					        //await inventory.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -134,13 +214,21 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
    xpBasedLevelCapDisabled: boolean,
 | 
					    xpBasedLevelCapDisabled: boolean,
 | 
				
			||||||
    buildLabel: string | undefined
 | 
					    buildLabel: string | undefined
 | 
				
			||||||
): Promise<IInventoryClient> => {
 | 
					): Promise<IInventoryClient> => {
 | 
				
			||||||
    const [inventoryWithLoadOutPresets, ships] = await Promise.all([
 | 
					    const [inventoryWithLoadOutPresets, ships, latestMessage] = await Promise.all([
 | 
				
			||||||
        inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
 | 
					        inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
 | 
				
			||||||
        Ship.find({ ShipOwnerId: inventory.accountOwnerId })
 | 
					        Ship.find({ ShipOwnerId: inventory.accountOwnerId }),
 | 
				
			||||||
 | 
					        Inbox.findOne({ ownerId: inventory.accountOwnerId }, "_id").sort({ date: -1 })
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
    const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
 | 
					    const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
 | 
				
			||||||
    inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
 | 
					    inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // In case mission inventory update added an inbox message, we need to send the Mailbox part so the client knows to refresh it.
 | 
				
			||||||
 | 
					    if (latestMessage) {
 | 
				
			||||||
 | 
					        inventoryResponse.Mailbox = {
 | 
				
			||||||
 | 
					            LastInboxId: toOid(latestMessage._id)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.infiniteCredits) {
 | 
					    if (config.infiniteCredits) {
 | 
				
			||||||
        inventoryResponse.RegularCredits = 999999999;
 | 
					        inventoryResponse.RegularCredits = 999999999;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -167,18 +255,6 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.unlockAllMissions) {
 | 
					 | 
				
			||||||
        inventoryResponse.Missions = [];
 | 
					 | 
				
			||||||
        for (const tag of Object.keys(ExportRegions)) {
 | 
					 | 
				
			||||||
            inventoryResponse.Missions.push({
 | 
					 | 
				
			||||||
                Completes: 1,
 | 
					 | 
				
			||||||
                Tier: 1,
 | 
					 | 
				
			||||||
                Tag: tag
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (config.unlockAllShipDecorations) {
 | 
					    if (config.unlockAllShipDecorations) {
 | 
				
			||||||
        inventoryResponse.ShipDecorations = [];
 | 
					        inventoryResponse.ShipDecorations = [];
 | 
				
			||||||
        for (const [uniqueName, item] of Object.entries(ExportResources)) {
 | 
					        for (const [uniqueName, item] of Object.entries(ExportResources)) {
 | 
				
			||||||
@ -300,9 +376,6 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
        applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
 | 
					        applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
 | 
					 | 
				
			||||||
    inventoryResponse.LastInventorySync = undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Set 2FA enabled so trading post can be used
 | 
					    // Set 2FA enabled so trading post can be used
 | 
				
			||||||
    inventoryResponse.HWIDProtectEnabled = true;
 | 
					    inventoryResponse.HWIDProtectEnabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -339,14 +412,41 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (config.unlockAllProfitTakerStages) {
 | 
				
			||||||
 | 
					        inventoryResponse.CompletedJobChains ??= [];
 | 
				
			||||||
 | 
					        const EudicoHeists = inventoryResponse.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
 | 
				
			||||||
 | 
					        if (EudicoHeists) {
 | 
				
			||||||
 | 
					            EudicoHeists.Jobs = allEudicoHeistJobs;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            inventoryResponse.CompletedJobChains.push({
 | 
				
			||||||
 | 
					                LocationTag: "EudicoHeists",
 | 
				
			||||||
 | 
					                Jobs: allEudicoHeistJobs
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (config.unlockAllSimarisResearchEntries) {
 | 
				
			||||||
 | 
					        inventoryResponse.LibraryPersonalTarget = undefined;
 | 
				
			||||||
 | 
					        inventoryResponse.LibraryPersonalProgress = [
 | 
				
			||||||
 | 
					            "/Lotus/Types/Game/Library/Targets/Research1Target",
 | 
				
			||||||
 | 
					            "/Lotus/Types/Game/Library/Targets/Research2Target",
 | 
				
			||||||
 | 
					            "/Lotus/Types/Game/Library/Targets/Research3Target",
 | 
				
			||||||
 | 
					            "/Lotus/Types/Game/Library/Targets/Research4Target",
 | 
				
			||||||
 | 
					            "/Lotus/Types/Game/Library/Targets/Research5Target",
 | 
				
			||||||
 | 
					            "/Lotus/Types/Game/Library/Targets/Research6Target",
 | 
				
			||||||
 | 
					            "/Lotus/Types/Game/Library/Targets/Research7Target"
 | 
				
			||||||
 | 
					        ].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return inventoryResponse;
 | 
					    return inventoryResponse;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const addString = (arr: string[], str: string): void => {
 | 
					const allEudicoHeistJobs = [
 | 
				
			||||||
    if (arr.indexOf(str) == -1) {
 | 
					    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
 | 
				
			||||||
        arr.push(str);
 | 
					    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
 | 
				
			||||||
    }
 | 
					    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
 | 
				
			||||||
};
 | 
					    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getExpRequiredForMr = (rank: number): number => {
 | 
					const getExpRequiredForMr = (rank: number): number => {
 | 
				
			||||||
    if (rank <= 30) {
 | 
					    if (rank <= 30) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,8 @@
 | 
				
			|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
					import { getInventory, updateCurrency, updateSlots } from "@/src/services/inventoryService";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { updateSlots } from "@/src/services/inventoryService";
 | 
					 | 
				
			||||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { exhaustive } from "@/src/utils/ts-utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
    loadout slots are additionally purchased slots only
 | 
					    loadout slots are additionally purchased slots only
 | 
				
			||||||
@ -23,13 +22,44 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
 | 
					    const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) {
 | 
					    let price;
 | 
				
			||||||
        logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
 | 
					    let amount;
 | 
				
			||||||
 | 
					    switch (body.Bin) {
 | 
				
			||||||
 | 
					        case InventorySlot.SUITS:
 | 
				
			||||||
 | 
					        case InventorySlot.MECHSUITS:
 | 
				
			||||||
 | 
					        case InventorySlot.PVE_LOADOUTS:
 | 
				
			||||||
 | 
					        case InventorySlot.CREWMEMBERS:
 | 
				
			||||||
 | 
					            price = 20;
 | 
				
			||||||
 | 
					            amount = 1;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case InventorySlot.SPACESUITS:
 | 
				
			||||||
 | 
					            price = 12;
 | 
				
			||||||
 | 
					            amount = 1;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case InventorySlot.WEAPONS:
 | 
				
			||||||
 | 
					        case InventorySlot.SPACEWEAPONS:
 | 
				
			||||||
 | 
					        case InventorySlot.SENTINELS:
 | 
				
			||||||
 | 
					        case InventorySlot.RJ_COMPONENT_AND_ARMAMENTS:
 | 
				
			||||||
 | 
					        case InventorySlot.AMPS:
 | 
				
			||||||
 | 
					            price = 12;
 | 
				
			||||||
 | 
					            amount = 2;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case InventorySlot.RIVENS:
 | 
				
			||||||
 | 
					            price = 60;
 | 
				
			||||||
 | 
					            amount = 3;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            exhaustive(body.Bin);
 | 
				
			||||||
 | 
					            throw new Error(`unexpected slot purchase of type ${body.Bin as string}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const currencyChanges = updateCurrency(inventory, 20, true);
 | 
					    const currencyChanges = updateCurrency(inventory, price, true);
 | 
				
			||||||
    updateSlots(inventory, body.Bin, 1, 1);
 | 
					    updateSlots(inventory, body.Bin, amount, amount);
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({ InventoryChanges: currencyChanges });
 | 
					    res.json({ InventoryChanges: currencyChanges });
 | 
				
			||||||
 | 
				
			|||||||
@ -4,16 +4,16 @@ import { config } from "@/src/services/configService";
 | 
				
			|||||||
import { buildConfig } from "@/src/services/buildConfigService";
 | 
					import { buildConfig } from "@/src/services/buildConfigService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Account } from "@/src/models/loginModel";
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
 | 
					import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
 | 
				
			||||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
 | 
					import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
 | 
					import { version_compare } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const loginController: RequestHandler = async (request, response) => {
 | 
					export const loginController: RequestHandler = async (request, response) => {
 | 
				
			||||||
    const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
 | 
					    const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const account = await Account.findOne({ email: loginRequest.email });
 | 
					    const account = await Account.findOne({ email: loginRequest.email });
 | 
				
			||||||
    const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const buildLabel: string =
 | 
					    const buildLabel: string =
 | 
				
			||||||
        typeof request.query.buildLabel == "string"
 | 
					        typeof request.query.buildLabel == "string"
 | 
				
			||||||
@ -42,26 +42,14 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
				
			|||||||
            loginRequest.ClientType == "webui-register")
 | 
					            loginRequest.ClientType == "webui-register")
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
 | 
					            const name = await getUsernameFromEmail(loginRequest.email);
 | 
				
			||||||
            let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
 | 
					 | 
				
			||||||
            if (await isNameTaken(name)) {
 | 
					 | 
				
			||||||
                let suffix = 0;
 | 
					 | 
				
			||||||
                do {
 | 
					 | 
				
			||||||
                    ++suffix;
 | 
					 | 
				
			||||||
                    name = nameFromEmail + suffix;
 | 
					 | 
				
			||||||
                } while (await isNameTaken(name));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const newAccount = await createAccount({
 | 
					            const newAccount = await createAccount({
 | 
				
			||||||
                email: loginRequest.email,
 | 
					                email: loginRequest.email,
 | 
				
			||||||
                password: loginRequest.password,
 | 
					                password: loginRequest.password,
 | 
				
			||||||
                DisplayName: name,
 | 
					                DisplayName: name,
 | 
				
			||||||
                CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
 | 
					                CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
 | 
				
			||||||
                ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
 | 
					                ClientType: loginRequest.ClientType,
 | 
				
			||||||
                CrossPlatformAllowed: true,
 | 
					                Nonce: createNonce(),
 | 
				
			||||||
                ForceLogoutVersion: 0,
 | 
					 | 
				
			||||||
                ConsentNeeded: false,
 | 
					 | 
				
			||||||
                TrackedSettings: [],
 | 
					 | 
				
			||||||
                Nonce: nonce,
 | 
					 | 
				
			||||||
                BuildLabel: buildLabel,
 | 
					                BuildLabel: buildLabel,
 | 
				
			||||||
                LastLogin: new Date()
 | 
					                LastLogin: new Date()
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -80,38 +68,29 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (loginRequest.ClientType == "webui-register") {
 | 
					 | 
				
			||||||
        response.status(400).json({ error: "account already exists" });
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!isCorrectPassword(loginRequest.password, account.password)) {
 | 
					    if (!isCorrectPassword(loginRequest.password, account.password)) {
 | 
				
			||||||
        response.status(400).json({ error: "incorrect login data" });
 | 
					        response.status(400).json({ error: "incorrect login data" });
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (loginRequest.ClientType == "webui") {
 | 
					    if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
 | 
				
			||||||
        if (!account.Nonce) {
 | 
					        // U17 seems to handle "nonce still set" like a login failure.
 | 
				
			||||||
            account.ClientType = "webui";
 | 
					        if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
 | 
				
			||||||
            account.Nonce = nonce;
 | 
					            response.status(400).send({ error: "nonce still set" });
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
 | 
					 | 
				
			||||||
            // U17 seems to handle "nonce still set" like a login failure.
 | 
					 | 
				
			||||||
            if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
 | 
					 | 
				
			||||||
                response.status(400).send({ error: "nonce still set" });
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        account.ClientType = loginRequest.ClientType;
 | 
					 | 
				
			||||||
        account.Nonce = nonce;
 | 
					 | 
				
			||||||
        account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
 | 
					 | 
				
			||||||
        account.BuildLabel = buildLabel;
 | 
					 | 
				
			||||||
        account.LastLogin = new Date();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    account.ClientType = loginRequest.ClientType;
 | 
				
			||||||
 | 
					    account.Nonce = createNonce();
 | 
				
			||||||
 | 
					    account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
 | 
				
			||||||
 | 
					    account.BuildLabel = buildLabel;
 | 
				
			||||||
 | 
					    account.LastLogin = new Date();
 | 
				
			||||||
    await account.save();
 | 
					    await account.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Tell WebUI its nonce has been invalidated
 | 
				
			||||||
 | 
					    sendWsBroadcastTo(account._id.toString(), { logged_out: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
 | 
					    response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,8 @@ import {
 | 
				
			|||||||
    setAccountGotLoginRewardToday
 | 
					    setAccountGotLoginRewardToday
 | 
				
			||||||
} from "@/src/services/loginRewardService";
 | 
					} from "@/src/services/loginRewardService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
					export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const account = await getAccountForRequest(req);
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
@ -15,7 +17,7 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
 | 
					    const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
 | 
				
			||||||
    const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
 | 
					    const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (today == account.LastLoginRewardDate) {
 | 
					    if (today == account.LastLoginRewardDate || config.disableDailyTribute) {
 | 
				
			||||||
        res.json({
 | 
					        res.json({
 | 
				
			||||||
            DailyTributeInfo: {
 | 
					            DailyTributeInfo: {
 | 
				
			||||||
                IsMilestoneDay: isMilestoneDay,
 | 
					                IsMilestoneDay: isMilestoneDay,
 | 
				
			||||||
@ -46,10 +48,10 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        response.DailyTributeInfo.HasChosenReward = true;
 | 
					        response.DailyTributeInfo.HasChosenReward = true;
 | 
				
			||||||
        response.DailyTributeInfo.ChosenReward = randomRewards[0];
 | 
					        response.DailyTributeInfo.ChosenReward = randomRewards[0];
 | 
				
			||||||
        response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
 | 
					        response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
 | 
				
			||||||
        await inventory.save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setAccountGotLoginRewardToday(account);
 | 
					        setAccountGotLoginRewardToday(account);
 | 
				
			||||||
        await account.save();
 | 
					        await Promise.all([inventory.save(), account.save()]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.json(response);
 | 
					    res.json(response);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import {
 | 
				
			|||||||
} from "@/src/services/loginRewardService";
 | 
					} 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 { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					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";
 | 
				
			||||||
@ -34,11 +35,10 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
 | 
				
			|||||||
        chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
 | 
					        chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
 | 
				
			||||||
        inventoryChanges = await claimLoginReward(inventory, chosenReward);
 | 
					        inventoryChanges = await claimLoginReward(inventory, chosenReward);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setAccountGotLoginRewardToday(account);
 | 
					    setAccountGotLoginRewardToday(account);
 | 
				
			||||||
    await account.save();
 | 
					    await Promise.all([inventory.save(), account.save()]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        DailyTributeInfo: {
 | 
					        DailyTributeInfo: {
 | 
				
			||||||
            NewInventory: inventoryChanges,
 | 
					            NewInventory: inventoryChanges,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { Account } from "@/src/models/loginModel";
 | 
					import { Account } from "@/src/models/loginModel";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const logoutController: RequestHandler = async (req, res) => {
 | 
					export const logoutController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    if (!req.query.accountId) {
 | 
					    if (!req.query.accountId) {
 | 
				
			||||||
@ -10,7 +11,7 @@ export const logoutController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        throw new Error("Request is missing nonce parameter");
 | 
					        throw new Error("Request is missing nonce parameter");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await Account.updateOne(
 | 
					    const stat = await Account.updateOne(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _id: req.query.accountId,
 | 
					            _id: req.query.accountId,
 | 
				
			||||||
            Nonce: nonce
 | 
					            Nonce: nonce
 | 
				
			||||||
@ -19,6 +20,10 @@ export const logoutController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            Nonce: 0
 | 
					            Nonce: 0
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					    if (stat.modifiedCount) {
 | 
				
			||||||
 | 
					        // Tell WebUI its nonce has been invalidated
 | 
				
			||||||
 | 
					        sendWsBroadcastTo(req.query.accountId as string, { logged_out: true });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.writeHead(200, {
 | 
					    res.writeHead(200, {
 | 
				
			||||||
        "Content-Type": "text/html",
 | 
					        "Content-Type": "text/html",
 | 
				
			||||||
 | 
				
			|||||||
@ -3,10 +3,12 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			|||||||
import { getAccountForRequest } from "@/src/services/loginService";
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
 | 
					import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
 | 
				
			||||||
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
 | 
					import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
 | 
				
			||||||
import { generateRewardSeed, getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getInventoryResponse } from "./inventoryController";
 | 
					import { getInventoryResponse } from "@/src/controllers/api/inventoryController";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
 | 
					import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					import { generateRewardSeed } from "@/src/services/rngService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
**** INPUT ****
 | 
					**** INPUT ****
 | 
				
			||||||
@ -76,6 +78,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
				
			|||||||
            InventoryJson: JSON.stringify(inventoryResponse),
 | 
					            InventoryJson: JSON.stringify(inventoryResponse),
 | 
				
			||||||
            MissionRewards: []
 | 
					            MissionRewards: []
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -86,7 +89,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
				
			|||||||
        AffiliationMods,
 | 
					        AffiliationMods,
 | 
				
			||||||
        SyndicateXPItemReward,
 | 
					        SyndicateXPItemReward,
 | 
				
			||||||
        ConquestCompletedMissionsCount
 | 
					        ConquestCompletedMissionsCount
 | 
				
			||||||
    } = await addMissionRewards(inventory, missionReport, firstCompletion);
 | 
					    } = await addMissionRewards(account, inventory, missionReport, firstCompletion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (missionReport.EndOfMatchUpload) {
 | 
					    if (missionReport.EndOfMatchUpload) {
 | 
				
			||||||
        inventory.RewardSeed = generateRewardSeed();
 | 
					        inventory.RewardSeed = generateRewardSeed();
 | 
				
			||||||
@ -106,6 +109,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
				
			|||||||
        AffiliationMods,
 | 
					        AffiliationMods,
 | 
				
			||||||
        ConquestCompletedMissionsCount
 | 
					        ConquestCompletedMissionsCount
 | 
				
			||||||
    } satisfies IMissionInventoryUpdateResponse);
 | 
					    } satisfies IMissionInventoryUpdateResponse);
 | 
				
			||||||
 | 
					    sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 | 
				
			|||||||
@ -15,10 +15,9 @@ import {
 | 
				
			|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { getDefaultUpgrades } from "@/src/services/itemDataService";
 | 
					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 { getRandomInt } from "@/src/services/rngService";
 | 
					import { getRandomInt } from "@/src/services/rngService";
 | 
				
			||||||
import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
 | 
					import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
 | 
				
			||||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IEquipmentDatabase, Status } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IModularCraftRequest {
 | 
					interface IModularCraftRequest {
 | 
				
			||||||
    WeaponType: string;
 | 
					    WeaponType: string;
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ import { ExportWeapons } from "warframe-public-export-plus";
 | 
				
			|||||||
import { IMongoDate } from "@/src/types/commonTypes";
 | 
					import { IMongoDate } from "@/src/types/commonTypes";
 | 
				
			||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
					import { toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
import { SRng } from "@/src/services/rngService";
 | 
					import { SRng } from "@/src/services/rngService";
 | 
				
			||||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    addEquipment,
 | 
					    addEquipment,
 | 
				
			||||||
@ -17,6 +17,7 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
 | 
				
			|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
					import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { EquipmentFeatures } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const modularWeaponSaleController: RequestHandler = async (req, res) => {
 | 
					export const modularWeaponSaleController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const partTypeToParts: Record<string, string[]> = {};
 | 
					    const partTypeToParts: Record<string, string[]> = {};
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			|||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
					import { getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface INameWeaponRequest {
 | 
					interface INameWeaponRequest {
 | 
				
			||||||
    ItemName: string;
 | 
					    ItemName: string;
 | 
				
			||||||
@ -27,4 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        InventoryChanges: currencyChanges
 | 
					        InventoryChanges: currencyChanges
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,27 +1,28 @@
 | 
				
			|||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
 | 
					import { version_compare } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    consumeModCharge,
 | 
					    antivirusMods,
 | 
				
			||||||
    decodeNemesisGuess,
 | 
					    decodeNemesisGuess,
 | 
				
			||||||
    encodeNemesisGuess,
 | 
					    encodeNemesisGuess,
 | 
				
			||||||
    getInfNodes,
 | 
					    getInfNodes,
 | 
				
			||||||
    getKnifeUpgrade,
 | 
					    getKnifeUpgrade,
 | 
				
			||||||
    getNemesisManifest,
 | 
					    getNemesisManifest,
 | 
				
			||||||
    getNemesisPasscode,
 | 
					    getNemesisPasscode,
 | 
				
			||||||
    getNemesisPasscodeModTypes,
 | 
					 | 
				
			||||||
    GUESS_CORRECT,
 | 
					    GUESS_CORRECT,
 | 
				
			||||||
    GUESS_INCORRECT,
 | 
					    GUESS_INCORRECT,
 | 
				
			||||||
    GUESS_NEUTRAL,
 | 
					    GUESS_NEUTRAL,
 | 
				
			||||||
    GUESS_NONE,
 | 
					    GUESS_NONE,
 | 
				
			||||||
    GUESS_WILDCARD,
 | 
					    GUESS_WILDCARD,
 | 
				
			||||||
    IKnifeResponse
 | 
					    IKnifeResponse,
 | 
				
			||||||
 | 
					    parseUpgrade
 | 
				
			||||||
} from "@/src/helpers/nemesisHelpers";
 | 
					} from "@/src/helpers/nemesisHelpers";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
					import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
				
			||||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
 | 
					import { addMods, freeUpSlot, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountForRequest } from "@/src/services/loginService";
 | 
					import { getAccountForRequest } 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 { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IEquipmentClient } from "@/src/types/equipmentTypes";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    IInnateDamageFingerprint,
 | 
					    IInnateDamageFingerprint,
 | 
				
			||||||
    IInventoryClient,
 | 
					    IInventoryClient,
 | 
				
			||||||
@ -35,6 +36,7 @@ import {
 | 
				
			|||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					} 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";
 | 
				
			||||||
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const nemesisController: RequestHandler = async (req, res) => {
 | 
					export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const account = await getAccountForRequest(req);
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
@ -134,34 +136,38 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                for (const upgrade of body.knife!.AttachedUpgrades) {
 | 
					                for (const upgrade of body.knife!.AttachedUpgrades) {
 | 
				
			||||||
                    switch (upgrade.ItemType) {
 | 
					                    switch (upgrade.ItemType) {
 | 
				
			||||||
                        case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
 | 
					                        case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
 | 
				
			||||||
                            antivirusGain += 10;
 | 
					 | 
				
			||||||
                            consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                        case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
 | 
					                        case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
 | 
				
			||||||
 | 
					                        case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
 | 
				
			||||||
                            antivirusGain += 10;
 | 
					                            antivirusGain += 10;
 | 
				
			||||||
                            consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
					                            consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
 | 
					                        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
 | 
					                        case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
 | 
				
			||||||
                            antivirusGain += 15;
 | 
					                            antivirusGain += 15;
 | 
				
			||||||
                            consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
					                            consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
 | 
					 | 
				
			||||||
                            antivirusGain += 10;
 | 
					 | 
				
			||||||
                            consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                inventory.Nemesis!.HenchmenKilled += antivirusGain;
 | 
					                inventory.Nemesis!.HenchmenKilled += antivirusGain;
 | 
				
			||||||
 | 
					                if (inventory.Nemesis!.HenchmenKilled >= 100) {
 | 
				
			||||||
 | 
					                    inventory.Nemesis!.HenchmenKilled = 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Weaken nemesis now.
 | 
				
			||||||
 | 
					                    inventory.Nemesis!.InfNodes = [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
 | 
				
			||||||
 | 
					                            Influence: 1
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ];
 | 
				
			||||||
 | 
					                    inventory.Nemesis!.Weakened = true;
 | 
				
			||||||
 | 
					                    const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, antivirusMods[passcode]);
 | 
				
			||||||
 | 
					                    consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (inventory.Nemesis!.HenchmenKilled >= 100) {
 | 
					            if (inventory.Nemesis!.HenchmenKilled < 100) {
 | 
				
			||||||
                inventory.Nemesis!.HenchmenKilled = 100;
 | 
					                inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json(response);
 | 
					            res.json(response);
 | 
				
			||||||
@ -198,16 +204,40 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            guess[body.position].result = correct ? GUESS_CORRECT : GUESS_INCORRECT;
 | 
					            guess[body.position].result = correct ? GUESS_CORRECT : GUESS_INCORRECT;
 | 
				
			||||||
            inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1] = encodeNemesisGuess(guess);
 | 
					            inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1] = encodeNemesisGuess(guess);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Increase rank if incorrect
 | 
					            const response: INemesisRequiemResponse = {};
 | 
				
			||||||
            let RankIncrease: number | undefined;
 | 
					            if (correct) {
 | 
				
			||||||
            if (!correct) {
 | 
					                if (body.position == 2) {
 | 
				
			||||||
                RankIncrease = 1;
 | 
					                    // That was all 3 guesses correct, nemesis is now weakened.
 | 
				
			||||||
 | 
					                    inventory.Nemesis!.InfNodes = [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
 | 
				
			||||||
 | 
					                            Influence: 1
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ];
 | 
				
			||||||
 | 
					                    inventory.Nemesis!.Weakened = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Subtract a charge from all requiem mods installed on parazon
 | 
				
			||||||
 | 
					                    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!;
 | 
				
			||||||
 | 
					                    for (let i = 3; i != 6; ++i) {
 | 
				
			||||||
 | 
					                        //logger.debug(`subtracting a charge from ${dataknifeUpgrades[i]}`);
 | 
				
			||||||
 | 
					                        const upgrade = parseUpgrade(inventory, dataknifeUpgrades[i]);
 | 
				
			||||||
 | 
					                        consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Guess was incorrect, increase rank
 | 
				
			||||||
 | 
					                response.RankIncrease = 1;
 | 
				
			||||||
                const manifest = getNemesisManifest(inventory.Nemesis!.manifest);
 | 
					                const manifest = getNemesisManifest(inventory.Nemesis!.manifest);
 | 
				
			||||||
                inventory.Nemesis!.Rank = Math.min(inventory.Nemesis!.Rank + 1, manifest.systemIndexes.length - 1);
 | 
					                inventory.Nemesis!.Rank = Math.min(inventory.Nemesis!.Rank + 1, manifest.systemIndexes.length - 1);
 | 
				
			||||||
                inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank);
 | 
					                inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json({ RankIncrease });
 | 
					            res.json(response);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else if ((req.query.mode as string) == "rs") {
 | 
					    } else if ((req.query.mode as string) == "rs") {
 | 
				
			||||||
        // report spawn; POST but no application data in body
 | 
					        // report spawn; POST but no application data in body
 | 
				
			||||||
@ -277,36 +307,15 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            target: inventory.toJSON().Nemesis
 | 
					            target: inventory.toJSON().Nemesis
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    } else if ((req.query.mode as string) == "w") {
 | 
					    } else if ((req.query.mode as string) == "w") {
 | 
				
			||||||
        const inventory = await getInventory(
 | 
					        const inventory = await getInventory(account._id.toString(), "Nemesis");
 | 
				
			||||||
            account._id.toString(),
 | 
					 | 
				
			||||||
            "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        //const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
 | 
					        //const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        inventory.Nemesis!.InfNodes = [
 | 
					        // As of 38.6.0, this request is no longer sent, instead mode=r already weakens the nemesis if appropriate.
 | 
				
			||||||
            {
 | 
					        // We always weaken the nemesis in mode=r so simply giving the client back the nemesis.
 | 
				
			||||||
                Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
 | 
					 | 
				
			||||||
                Influence: 1
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
        inventory.Nemesis!.Weakened = true;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const response: IKnifeResponse & { target: INemesisClient } = {
 | 
					        const response: INemesisWeakenResponse = {
 | 
				
			||||||
            target: inventory.toJSON<IInventoryClient>().Nemesis!
 | 
					            target: inventory.toJSON<IInventoryClient>().Nemesis!
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Consume charge of the correct requiem mod(s)
 | 
					 | 
				
			||||||
        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 modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!);
 | 
					 | 
				
			||||||
        for (const modType of modTypes) {
 | 
					 | 
				
			||||||
            const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType);
 | 
					 | 
				
			||||||
            consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await inventory.save();
 | 
					 | 
				
			||||||
        res.json(response);
 | 
					        res.json(response);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
					        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
@ -362,11 +371,19 @@ interface INemesisRequiemRequest {
 | 
				
			|||||||
    knife?: IKnife;
 | 
					    knife?: IKnife;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface INemesisRequiemResponse extends IKnifeResponse {
 | 
				
			||||||
 | 
					    RankIncrease?: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// interface INemesisWeakenRequest {
 | 
					// interface INemesisWeakenRequest {
 | 
				
			||||||
//     target: INemesisClient;
 | 
					//     target: INemesisClient;
 | 
				
			||||||
//     knife: IKnife;
 | 
					//     knife: IKnife;
 | 
				
			||||||
// }
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface INemesisWeakenResponse extends IKnifeResponse {
 | 
				
			||||||
 | 
					    target: INemesisClient;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IKnife {
 | 
					interface IKnife {
 | 
				
			||||||
    Item: IEquipmentClient;
 | 
					    Item: IEquipmentClient;
 | 
				
			||||||
    Skins: IWeaponSkinClient[];
 | 
					    Skins: IWeaponSkinClient[];
 | 
				
			||||||
@ -375,3 +392,54 @@ interface IKnife {
 | 
				
			|||||||
    AttachedUpgrades: IUpgradeClient[];
 | 
					    AttachedUpgrades: IUpgradeClient[];
 | 
				
			||||||
    HiddenWhenHolstered: boolean;
 | 
					    HiddenWhenHolstered: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const consumeModCharge = (
 | 
				
			||||||
 | 
					    response: IKnifeResponse,
 | 
				
			||||||
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
 | 
					    upgrade: { ItemId: IOid; ItemType: string },
 | 
				
			||||||
 | 
					    dataknifeUpgrades: string[]
 | 
				
			||||||
 | 
					): void => {
 | 
				
			||||||
 | 
					    response.UpgradeIds ??= [];
 | 
				
			||||||
 | 
					    response.UpgradeTypes ??= [];
 | 
				
			||||||
 | 
					    response.UpgradeFingerprints ??= [];
 | 
				
			||||||
 | 
					    response.UpgradeNew ??= [];
 | 
				
			||||||
 | 
					    response.HasKnife = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (upgrade.ItemId.$oid != "000000000000000000000000") {
 | 
				
			||||||
 | 
					        const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
 | 
				
			||||||
 | 
					        const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
 | 
				
			||||||
 | 
					        fingerprint.lvl += 1;
 | 
				
			||||||
 | 
					        dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response.UpgradeIds.push(upgrade.ItemId.$oid);
 | 
				
			||||||
 | 
					        response.UpgradeTypes.push(upgrade.ItemType);
 | 
				
			||||||
 | 
					        response.UpgradeFingerprints.push(fingerprint);
 | 
				
			||||||
 | 
					        response.UpgradeNew.push(false);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const id = new Types.ObjectId();
 | 
				
			||||||
 | 
					        inventory.Upgrades.push({
 | 
				
			||||||
 | 
					            _id: id,
 | 
				
			||||||
 | 
					            ItemType: upgrade.ItemType,
 | 
				
			||||||
 | 
					            UpgradeFingerprint: `{"lvl":1}`
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        addMods(inventory, [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ItemType: upgrade.ItemType,
 | 
				
			||||||
 | 
					                ItemCount: -1
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType);
 | 
				
			||||||
 | 
					        if (dataknifeRawUpgradeIndex != -1) {
 | 
				
			||||||
 | 
					            dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            logger.warn(`${upgrade.ItemType} not found in dataknife config`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response.UpgradeIds.push(id.toString());
 | 
				
			||||||
 | 
					        response.UpgradeTypes.push(upgrade.ItemType);
 | 
				
			||||||
 | 
					        response.UpgradeFingerprints.push({ lvl: 1 });
 | 
				
			||||||
 | 
					        response.UpgradeNew.push(true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -57,7 +57,15 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
 | 
				
			|||||||
                component.DecoCapacity -= meta.capacityCost;
 | 
					                component.DecoCapacity -= meta.capacityCost;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
 | 
					            const entry = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type);
 | 
				
			||||||
 | 
					            if (!entry) {
 | 
				
			||||||
 | 
					                throw new Error(`unknown deco type: ${deco.Type}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const [itemType, meta] = entry;
 | 
				
			||||||
 | 
					            if (meta.dojoCapacityCost === undefined) {
 | 
				
			||||||
 | 
					                throw new Error(`unknown deco type: ${deco.Type}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            component.DecoCapacity -= meta.dojoCapacityCost;
 | 
				
			||||||
            if (deco.Sockets !== undefined) {
 | 
					            if (deco.Sockets !== undefined) {
 | 
				
			||||||
                guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
 | 
					                guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
 | 
				
			||||||
                    1;
 | 
					                    1;
 | 
				
			||||||
@ -71,7 +79,13 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
 | 
				
			|||||||
                if (meta) {
 | 
					                if (meta) {
 | 
				
			||||||
                    processDojoBuildMaterialsGathered(guild, meta);
 | 
					                    processDojoBuildMaterialsGathered(guild, meta);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
 | 
					            } else if (
 | 
				
			||||||
 | 
					                deco.Type.startsWith("/Lotus/Objects/Tenno/Dojo/NpcPlaceables/") ||
 | 
				
			||||||
 | 
					                (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems)
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                if (!guild.VaultRegularCredits || !guild.VaultMiscItems) {
 | 
				
			||||||
 | 
					                    throw new Error(`dojo visitor placed without anything in vault?!`);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
 | 
					                if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
 | 
				
			||||||
                    let enoughMiscItems = true;
 | 
					                    let enoughMiscItems = true;
 | 
				
			||||||
                    for (const ingredient of meta.ingredients) {
 | 
					                    for (const ingredient of meta.ingredients) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,25 +1,39 @@
 | 
				
			|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { addConsumables, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const playerSkillsController: RequestHandler = async (req, res) => {
 | 
					export const playerSkillsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId, "PlayerSkills");
 | 
					    const inventory = await getInventory(accountId, "PlayerSkills Consumables");
 | 
				
			||||||
    const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
 | 
					    const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
 | 
					    const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
 | 
				
			||||||
    const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000;
 | 
					    const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000;
 | 
				
			||||||
    inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost;
 | 
					    inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost;
 | 
				
			||||||
    inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
 | 
					    inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
 | 
				
			||||||
    await inventory.save();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					    if (request.Skill == "LPS_COMMAND" && inventory.PlayerSkills.LPS_COMMAND == 9) {
 | 
				
			||||||
 | 
					        const consumablesChanges = [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ItemType: "/Lotus/Types/Restoratives/Consumable/CrewmateBall",
 | 
				
			||||||
 | 
					                ItemCount: 1
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					        addConsumables(inventory, consumablesChanges);
 | 
				
			||||||
 | 
					        inventoryChanges.Consumables = consumablesChanges;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        Pool: request.Pool,
 | 
					        Pool: request.Pool,
 | 
				
			||||||
        PoolInc: -cost,
 | 
					        PoolInc: -cost,
 | 
				
			||||||
        Skill: request.Skill,
 | 
					        Skill: request.Skill,
 | 
				
			||||||
        Rank: oldRank + 1
 | 
					        Rank: oldRank + 1,
 | 
				
			||||||
 | 
					        InventoryChanges: inventoryChanges
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			|||||||
import { IPurchaseRequest } from "@/src/types/purchaseTypes";
 | 
					import { IPurchaseRequest } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { handlePurchase } from "@/src/services/purchaseService";
 | 
					import { handlePurchase } from "@/src/services/purchaseService";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const purchaseController: RequestHandler = async (req, res) => {
 | 
					export const purchaseController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
 | 
					    const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
 | 
				
			||||||
@ -10,5 +11,7 @@ export const purchaseController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const response = await handlePurchase(purchaseRequest, inventory);
 | 
					    const response = await handlePurchase(purchaseRequest, inventory);
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    //console.log(JSON.stringify(response, null, 2));
 | 
				
			||||||
    res.json(response);
 | 
					    res.json(response);
 | 
				
			||||||
 | 
					    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
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";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const renamePetController: RequestHandler = async (req, res) => {
 | 
					export const renamePetController: RequestHandler = async (req, res) => {
 | 
				
			||||||
@ -8,13 +10,20 @@ export const renamePetController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const inventory = await getInventory(accountId, "KubrowPets PremiumCredits PremiumCreditsFree");
 | 
					    const inventory = await getInventory(accountId, "KubrowPets PremiumCredits PremiumCreditsFree");
 | 
				
			||||||
    const data = getJSONfromString<IRenamePetRequest>(String(req.body));
 | 
					    const data = getJSONfromString<IRenamePetRequest>(String(req.body));
 | 
				
			||||||
    const details = inventory.KubrowPets.id(data.petId)!.Details!;
 | 
					    const details = inventory.KubrowPets.id(data.petId)!.Details!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    details.Name = data.name;
 | 
					    details.Name = data.name;
 | 
				
			||||||
    const currencyChanges = updateCurrency(inventory, 15, true);
 | 
					
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					    if (!("webui" in req.query)) {
 | 
				
			||||||
 | 
					        updateCurrency(inventory, 15, true, inventoryChanges);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        ...data,
 | 
					        ...data,
 | 
				
			||||||
        inventoryChanges: currencyChanges
 | 
					        inventoryChanges: inventoryChanges
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IRenamePetRequest {
 | 
					interface IRenamePetRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
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 { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { Status } from "@/src/types/equipmentTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const retrievePetFromStasisController: RequestHandler = async (req, res) => {
 | 
					export const retrievePetFromStasisController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        inventory.DialogueHistory.Dialogues ??= [];
 | 
					        inventory.DialogueHistory.Dialogues ??= [];
 | 
				
			||||||
        const dialogue = getDialogue(inventory, request.DialogueName);
 | 
					        const dialogue = getDialogue(inventory, request.DialogueName);
 | 
				
			||||||
        dialogue.Rank = request.Rank;
 | 
					        dialogue.Rank = request.Rank;
 | 
				
			||||||
        dialogue.Chemistry = request.Chemistry;
 | 
					        dialogue.Chemistry += request.Chemistry;
 | 
				
			||||||
        dialogue.QueuedDialogues = request.QueuedDialogues;
 | 
					        dialogue.QueuedDialogues = request.QueuedDialogues;
 | 
				
			||||||
        for (const bool of request.Booleans) {
 | 
					        for (const bool of request.Booleans) {
 | 
				
			||||||
            dialogue.Booleans.push(bool);
 | 
					            dialogue.Booleans.push(bool);
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ISettings } from "../../types/inventoryTypes/inventoryTypes";
 | 
					import { ISettings } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ISaveSettingsRequest {
 | 
					interface ISaveSettingsRequest {
 | 
				
			||||||
    Settings: ISettings;
 | 
					    Settings: ISettings;
 | 
				
			||||||
 | 
				
			|||||||
@ -15,9 +15,11 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			|||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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;
 | 
				
			||||||
 | 
					    //console.log(JSON.stringify(payload, null, 2));
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
 | 
					    const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
 | 
				
			||||||
    if (payload.SellCurrency == "SC_RegularCredits") {
 | 
					    if (payload.SellCurrency == "SC_RegularCredits") {
 | 
				
			||||||
@ -57,6 +59,9 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    if (payload.Items.Hoverboards) {
 | 
					    if (payload.Items.Hoverboards) {
 | 
				
			||||||
        requiredFields.add(InventorySlot.SPACESUITS);
 | 
					        requiredFields.add(InventorySlot.SPACESUITS);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.CrewMembers) {
 | 
				
			||||||
 | 
					        requiredFields.add(InventorySlot.CREWMEMBERS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
 | 
					    if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
 | 
				
			||||||
        requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
 | 
					        requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
 | 
				
			||||||
        requiredFields.add("CrewShipRawSalvage");
 | 
					        requiredFields.add("CrewShipRawSalvage");
 | 
				
			||||||
@ -180,6 +185,17 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            inventory.Drones.pull({ _id: sellItem.String });
 | 
					            inventory.Drones.pull({ _id: sellItem.String });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.KubrowPetPrints) {
 | 
				
			||||||
 | 
					        payload.Items.KubrowPetPrints.forEach(sellItem => {
 | 
				
			||||||
 | 
					            inventory.KubrowPetPrints.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (payload.Items.CrewMembers) {
 | 
				
			||||||
 | 
					        payload.Items.CrewMembers.forEach(sellItem => {
 | 
				
			||||||
 | 
					            inventory.CrewMembers.pull({ _id: sellItem.String });
 | 
				
			||||||
 | 
					            freeUpSlot(inventory, InventorySlot.CREWMEMBERS);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (payload.Items.CrewShipWeapons) {
 | 
					    if (payload.Items.CrewShipWeapons) {
 | 
				
			||||||
        payload.Items.CrewShipWeapons.forEach(sellItem => {
 | 
					        payload.Items.CrewShipWeapons.forEach(sellItem => {
 | 
				
			||||||
            if (sellItem.String[0] == "/") {
 | 
					            if (sellItem.String[0] == "/") {
 | 
				
			||||||
@ -279,6 +295,7 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
 | 
					        inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ISellRequest {
 | 
					interface ISellRequest {
 | 
				
			||||||
@ -301,6 +318,8 @@ interface ISellRequest {
 | 
				
			|||||||
        OperatorAmps?: ISellItem[];
 | 
					        OperatorAmps?: ISellItem[];
 | 
				
			||||||
        Hoverboards?: ISellItem[];
 | 
					        Hoverboards?: ISellItem[];
 | 
				
			||||||
        Drones?: ISellItem[];
 | 
					        Drones?: ISellItem[];
 | 
				
			||||||
 | 
					        KubrowPetPrints?: ISellItem[];
 | 
				
			||||||
 | 
					        CrewMembers?: ISellItem[];
 | 
				
			||||||
        CrewShipWeapons?: ISellItem[];
 | 
					        CrewShipWeapons?: ISellItem[];
 | 
				
			||||||
        CrewShipWeaponSkins?: ISellItem[];
 | 
					        CrewShipWeaponSkins?: ISellItem[];
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
					import { getPersonalRooms } from "@/src/services/personalRoomsService";
 | 
				
			||||||
import { TBootLocation } from "@/src/types/shipTypes";
 | 
					import { TBootLocation } from "@/src/types/personalRoomsTypes";
 | 
				
			||||||
import { getInventory } from "@/src/services/inventoryService";
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setBootLocationController: RequestHandler = async (req, res) => {
 | 
					export const setBootLocationController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
 | 
					import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/personalRoomsTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
 | 
					import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { setShipCustomizations } from "@/src/services/shipCustomizationsService";
 | 
					import { setShipCustomizations } from "@/src/services/shipCustomizationsService";
 | 
				
			||||||
import { ISetShipCustomizationsRequest } from "@/src/types/shipTypes";
 | 
					import { ISetShipCustomizationsRequest } from "@/src/types/personalRoomsTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ 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";
 | 
					import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/personalRoomsTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
 | 
					export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								src/controllers/api/setSuitInfectionController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/controllers/api/setSuitInfectionController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IEquipmentClient } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setSuitInfectionController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "Suits");
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<ISetSuitInfectionRequest>(String(req.body));
 | 
				
			||||||
 | 
					    for (const clientSuit of payload.Suits) {
 | 
				
			||||||
 | 
					        const dbSuit = inventory.Suits.id(fromOid(clientSuit.ItemId))!;
 | 
				
			||||||
 | 
					        dbSuit.InfestationDate = fromMongoDate(clientSuit.InfestationDate!);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISetSuitInfectionRequest {
 | 
				
			||||||
 | 
					    Suits: IEquipmentClient[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { IShipDecorationsRequest } from "@/src/types/shipTypes";
 | 
					import { IShipDecorationsRequest } from "@/src/types/personalRoomsTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
 | 
					import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
 | 
				
			||||||
 | 
				
			|||||||
@ -45,9 +45,9 @@ export const startRecipeController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    for (let i = 0; i != recipe.ingredients.length; ++i) {
 | 
					    for (let i = 0; i != recipe.ingredients.length; ++i) {
 | 
				
			||||||
        if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
 | 
					        if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
 | 
				
			||||||
            if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
 | 
					            if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
 | 
				
			||||||
                const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
 | 
					                const index = inventory.KubrowPetEggs.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
 | 
				
			||||||
                if (index != -1) {
 | 
					                if (index != -1) {
 | 
				
			||||||
                    inventory.KubrowPetEggs!.splice(index, 1);
 | 
					                    inventory.KubrowPetEggs.splice(index, 1);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
 | 
					                const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
 | 
				
			||||||
@ -72,6 +72,10 @@ export const startRecipeController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
 | 
					    if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
 | 
				
			||||||
        inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType);
 | 
					        inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType);
 | 
				
			||||||
        pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId));
 | 
					        pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId));
 | 
				
			||||||
 | 
					    } else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
 | 
				
			||||||
 | 
					        pr.KubrowPet = new Types.ObjectId(startRecipeRequest.Ids[recipe.ingredients.length]);
 | 
				
			||||||
 | 
					        const pet = inventory.KubrowPets.id(pr.KubrowPet)!;
 | 
				
			||||||
 | 
					        pet.Details!.PrintsRemaining -= 1;
 | 
				
			||||||
    } else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
					    } else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
				
			||||||
        const spectreLoadout: ISpectreLoadout = {
 | 
					        const spectreLoadout: ISpectreLoadout = {
 | 
				
			||||||
            ItemType: recipe.resultType,
 | 
					            ItemType: recipe.resultType,
 | 
				
			||||||
 | 
				
			|||||||
@ -31,13 +31,13 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
 | 
				
			|||||||
        AffiliationTag: data.AffiliationTag,
 | 
					        AffiliationTag: data.AffiliationTag,
 | 
				
			||||||
        InventoryChanges: {},
 | 
					        InventoryChanges: {},
 | 
				
			||||||
        Level: data.SacrificeLevel,
 | 
					        Level: data.SacrificeLevel,
 | 
				
			||||||
        LevelIncrease: levelIncrease,
 | 
					        LevelIncrease: data.SacrificeLevel < 0 ? 1 : levelIncrease,
 | 
				
			||||||
        NewEpisodeReward: false
 | 
					        NewEpisodeReward: false
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Process sacrifices and rewards for every level we're reaching
 | 
					    // Process sacrifices and rewards for every level we're reaching
 | 
				
			||||||
    const manifest = ExportSyndicates[data.AffiliationTag];
 | 
					    const manifest = ExportSyndicates[data.AffiliationTag];
 | 
				
			||||||
    for (let level = oldLevel + levelIncrease; level <= data.SacrificeLevel; ++level) {
 | 
					    for (let level = oldLevel + Math.min(levelIncrease, 1); level <= data.SacrificeLevel; ++level) {
 | 
				
			||||||
        let sacrifice: ISyndicateSacrifice | undefined;
 | 
					        let sacrifice: ISyndicateSacrifice | undefined;
 | 
				
			||||||
        if (level == 0) {
 | 
					        if (level == 0) {
 | 
				
			||||||
            sacrifice = manifest.initiationSacrifice;
 | 
					            sacrifice = manifest.initiationSacrifice;
 | 
				
			||||||
@ -94,7 +94,7 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Commit
 | 
					    // Commit
 | 
				
			||||||
    syndicate.Title = data.SacrificeLevel;
 | 
					    syndicate.Title = data.SacrificeLevel < 0 ? data.SacrificeLevel + 1 : data.SacrificeLevel;
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response.json(res);
 | 
					    response.json(res);
 | 
				
			||||||
 | 
				
			|||||||
@ -5,8 +5,8 @@ import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTy
 | 
				
			|||||||
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 { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { EquipmentFeatures } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
 | 
					export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -54,13 +54,14 @@ export const syndicateStandingBonusController: RequestHandler = async (req, res)
 | 
				
			|||||||
        inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
 | 
					        inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const affiliationMod = addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, true);
 | 
					    const affiliationMods: IAffiliationMods[] = [];
 | 
				
			||||||
 | 
					    addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, affiliationMods, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        InventoryChanges: inventoryChanges,
 | 
					        InventoryChanges: inventoryChanges,
 | 
				
			||||||
        AffiliationMods: [affiliationMod]
 | 
					        AffiliationMods: affiliationMods
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										27
									
								
								src/controllers/api/umbraController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/controllers/api/umbraController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { addMiscItem, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IEquipmentClient } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const umbraController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "Suits MiscItems");
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<IUmbraRequest>(String(req.body));
 | 
				
			||||||
 | 
					    for (const clientSuit of payload.Suits) {
 | 
				
			||||||
 | 
					        const dbSuit = inventory.Suits.id(fromOid(clientSuit.ItemId))!;
 | 
				
			||||||
 | 
					        if (clientSuit.UmbraDate) {
 | 
				
			||||||
 | 
					            addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/UmbraEchoes", -1);
 | 
				
			||||||
 | 
					            dbSuit.UmbraDate = fromMongoDate(clientSuit.UmbraDate);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            dbSuit.UmbraDate = undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IUmbraRequest {
 | 
				
			||||||
 | 
					    Suits: IEquipmentClient[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,9 +1,11 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { getAccountForRequest } from "@/src/services/loginService";
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { addChallenges, getInventory } from "@/src/services/inventoryService";
 | 
					import { addCalendarProgress, addChallenges, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IAffiliationMods } from "@/src/types/purchaseTypes";
 | 
					import { IAffiliationMods } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { getEntriesUnsafe } from "@/src/utils/ts-utils";
 | 
				
			||||||
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
 | 
					export const updateChallengeProgressController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
 | 
					    const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
 | 
				
			||||||
@ -11,7 +13,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(
 | 
					    const inventory = await getInventory(
 | 
				
			||||||
        account._id.toString(),
 | 
					        account._id.toString(),
 | 
				
			||||||
        "ChallengeProgress SeasonChallengeHistory Affiliations"
 | 
					        "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress"
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    let affiliationMods: IAffiliationMods[] = [];
 | 
					    let affiliationMods: IAffiliationMods[] = [];
 | 
				
			||||||
    if (challenges.ChallengeProgress) {
 | 
					    if (challenges.ChallengeProgress) {
 | 
				
			||||||
@ -22,15 +24,39 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
 | 
				
			|||||||
            challenges.SeasonChallengeCompletions
 | 
					            challenges.SeasonChallengeCompletions
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (challenges.SeasonChallengeHistory) {
 | 
					    for (const [key, value] of getEntriesUnsafe(challenges)) {
 | 
				
			||||||
        challenges.SeasonChallengeHistory.forEach(({ challenge, id }) => {
 | 
					        if (value === undefined) {
 | 
				
			||||||
            const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
 | 
					            logger.error(`Challenge progress update key ${key} has no value`);
 | 
				
			||||||
            if (itemIndex !== -1) {
 | 
					            continue;
 | 
				
			||||||
                inventory.SeasonChallengeHistory[itemIndex].id = id;
 | 
					        }
 | 
				
			||||||
            } else {
 | 
					        switch (key) {
 | 
				
			||||||
                inventory.SeasonChallengeHistory.push({ challenge, id });
 | 
					            case "ChallengesFixVersion":
 | 
				
			||||||
            }
 | 
					                inventory.ChallengesFixVersion = value;
 | 
				
			||||||
        });
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case "SeasonChallengeHistory":
 | 
				
			||||||
 | 
					                value.forEach(({ challenge, id }) => {
 | 
				
			||||||
 | 
					                    const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
 | 
				
			||||||
 | 
					                    if (itemIndex !== -1) {
 | 
				
			||||||
 | 
					                        inventory.SeasonChallengeHistory[itemIndex].id = id;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        inventory.SeasonChallengeHistory.push({ challenge, id });
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case "CalendarProgress":
 | 
				
			||||||
 | 
					                addCalendarProgress(inventory, value);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case "ChallengeProgress":
 | 
				
			||||||
 | 
					            case "SeasonChallengeCompletions":
 | 
				
			||||||
 | 
					            case "ChallengePTS":
 | 
				
			||||||
 | 
					            case "crossPlaySetting":
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                logger.warn(`unknown challenge progress entry`, { key, value });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -40,7 +66,11 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IUpdateChallengeProgressRequest {
 | 
					interface IUpdateChallengeProgressRequest {
 | 
				
			||||||
 | 
					    ChallengePTS?: number;
 | 
				
			||||||
 | 
					    ChallengesFixVersion?: number;
 | 
				
			||||||
    ChallengeProgress?: IChallengeProgress[];
 | 
					    ChallengeProgress?: IChallengeProgress[];
 | 
				
			||||||
    SeasonChallengeHistory?: ISeasonChallenge[];
 | 
					    SeasonChallengeHistory?: ISeasonChallenge[];
 | 
				
			||||||
    SeasonChallengeCompletions?: ISeasonChallenge[];
 | 
					    SeasonChallengeCompletions?: ISeasonChallenge[];
 | 
				
			||||||
 | 
					    CalendarProgress?: { challenge: string }[];
 | 
				
			||||||
 | 
					    crossPlaySetting?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,6 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { IUpgradesRequest } from "@/src/types/requestTypes";
 | 
					import { IUpgradesRequest } from "@/src/types/requestTypes";
 | 
				
			||||||
import {
 | 
					import { ArtifactPolarity, IAbilityOverride } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
    ArtifactPolarity,
 | 
					 | 
				
			||||||
    IEquipmentDatabase,
 | 
					 | 
				
			||||||
    EquipmentFeatures,
 | 
					 | 
				
			||||||
    IAbilityOverride
 | 
					 | 
				
			||||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					 | 
				
			||||||
import { IInventoryClient, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IInventoryClient, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
@ -13,6 +8,8 @@ import { getRecipeByResult } from "@/src/services/itemDataService";
 | 
				
			|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
 | 
					import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					import { EquipmentFeatures, IEquipmentDatabase } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const upgradesController: RequestHandler = async (req, res) => {
 | 
					export const upgradesController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -120,6 +117,7 @@ export const upgradesController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                    setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
 | 
					                    setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
 | 
				
			||||||
                    item.Polarized ??= 0;
 | 
					                    item.Polarized ??= 0;
 | 
				
			||||||
                    item.Polarized += 1;
 | 
					                    item.Polarized += 1;
 | 
				
			||||||
 | 
					                    sendWsBroadcastTo(accountId, { update_inventory: true }); // webui may need to to re-add "max rank" button
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {
 | 
					                case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    const requests = req.body as IAddItemRequest[];
 | 
					    const requests = req.body as IAddItemRequest[];
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    for (const request of requests) {
 | 
					    for (const request of requests) {
 | 
				
			||||||
        await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, undefined, true);
 | 
					        await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, request.Fingerprint, true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
@ -16,4 +16,5 @@ export const addItemsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
interface IAddItemRequest {
 | 
					interface IAddItemRequest {
 | 
				
			||||||
    ItemType: string;
 | 
					    ItemType: string;
 | 
				
			||||||
    ItemCount: number;
 | 
					    ItemCount: number;
 | 
				
			||||||
 | 
					    Fingerprint?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { getInventory, addRecipes } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "Recipes");
 | 
				
			||||||
 | 
					    const allHelminthRecipes = Object.keys(ExportRecipes).filter(
 | 
				
			||||||
 | 
					        key => ExportRecipes[key].secretIngredientAction === "SIA_WARFRAME_ABILITY"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const inventoryHelminthRecipes = inventory.Recipes.filter(recipe =>
 | 
				
			||||||
 | 
					        recipe.ItemType.startsWith("/Lotus/Types/Recipes/AbilityOverrides/")
 | 
				
			||||||
 | 
					    ).map(recipe => recipe.ItemType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const missingHelminthRecipes = allHelminthRecipes
 | 
				
			||||||
 | 
					        .filter(key => !inventoryHelminthRecipes.includes(key))
 | 
				
			||||||
 | 
					        .map(ItemType => ({ ItemType, ItemCount: 1 }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    addRecipes(inventory, missingHelminthRecipes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
 | 
					import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { IOid } from "@/src/types/commonTypes";
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IEquipmentClient } from "@/src/types/equipmentTypes";
 | 
				
			||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { ExportMisc } from "warframe-public-export-plus";
 | 
					import { ExportMisc } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										40
									
								
								src/controllers/custom/completeAllMissionsController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/controllers/custom/completeAllMissionsController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					import { addString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { addFixedLevelRewards } from "@/src/services/missionInventoryUpdateService";
 | 
				
			||||||
 | 
					import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
 | 
					import { IMissionReward } from "@/src/types/missionTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportRegions } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const completeAllMissionsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					    const MissionRewards: IMissionReward[] = [];
 | 
				
			||||||
 | 
					    for (const [tag, node] of Object.entries(ExportRegions)) {
 | 
				
			||||||
 | 
					        let mission = inventory.Missions.find(x => x.Tag == tag);
 | 
				
			||||||
 | 
					        if (!mission) {
 | 
				
			||||||
 | 
					            mission =
 | 
				
			||||||
 | 
					                inventory.Missions[
 | 
				
			||||||
 | 
					                    inventory.Missions.push({
 | 
				
			||||||
 | 
					                        Completes: 0,
 | 
				
			||||||
 | 
					                        Tier: 0,
 | 
				
			||||||
 | 
					                        Tag: tag
 | 
				
			||||||
 | 
					                    }) - 1
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (mission.Completes == 0) {
 | 
				
			||||||
 | 
					            mission.Completes++;
 | 
				
			||||||
 | 
					            if (node.missionReward) {
 | 
				
			||||||
 | 
					                addFixedLevelRewards(node.missionReward, MissionRewards);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        mission.Tier = 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (const reward of MissionRewards) {
 | 
				
			||||||
 | 
					        await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    addString(inventory.NodeIntrosCompleted, "TeshinHardModeUnlocked");
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/controllers/custom/configController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/controllers/custom/configController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { saveConfig } from "@/src/services/configWriterService";
 | 
				
			||||||
 | 
					import { sendWsBroadcastExcept } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getConfigController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    if (isAdministrator(account)) {
 | 
				
			||||||
 | 
					        const responseData: Record<string, boolean | string | number | null> = {};
 | 
				
			||||||
 | 
					        for (const id of req.body as string[]) {
 | 
				
			||||||
 | 
					            const [obj, idx] = configIdToIndexable(id);
 | 
				
			||||||
 | 
					            responseData[id] = obj[idx] ?? null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        res.json(responseData);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        res.status(401).end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setConfigController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
					    if (isAdministrator(account)) {
 | 
				
			||||||
 | 
					        for (const [id, value] of Object.entries(req.body as Record<string, boolean | string | number>)) {
 | 
				
			||||||
 | 
					            const [obj, idx] = configIdToIndexable(id);
 | 
				
			||||||
 | 
					            obj[idx] = value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true });
 | 
				
			||||||
 | 
					        await saveConfig();
 | 
				
			||||||
 | 
					        res.end();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        res.status(401).end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const configIdToIndexable = (id: string): [Record<string, boolean | string | number | undefined>, string] => {
 | 
				
			||||||
 | 
					    let obj = config as unknown as Record<string, never>;
 | 
				
			||||||
 | 
					    const arr = id.split(".");
 | 
				
			||||||
 | 
					    while (arr.length > 1) {
 | 
				
			||||||
 | 
					        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
				
			||||||
 | 
					        obj[arr[0]] ??= {} as never;
 | 
				
			||||||
 | 
					        obj = obj[arr[0]];
 | 
				
			||||||
 | 
					        arr.splice(0, 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return [obj, arr[0]];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,14 +0,0 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					 | 
				
			||||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getConfigDataController: RequestHandler = async (req, res) => {
 | 
					 | 
				
			||||||
    const account = await getAccountForRequest(req);
 | 
					 | 
				
			||||||
    if (isAdministrator(account)) {
 | 
					 | 
				
			||||||
        res.json(config);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        res.status(401).end();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { getConfigDataController };
 | 
					 | 
				
			||||||
@ -20,8 +20,8 @@ import {
 | 
				
			|||||||
    ExportWeapons,
 | 
					    ExportWeapons,
 | 
				
			||||||
    TRelicQuality
 | 
					    TRelicQuality
 | 
				
			||||||
} from "warframe-public-export-plus";
 | 
					} from "warframe-public-export-plus";
 | 
				
			||||||
import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json";
 | 
					 | 
				
			||||||
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
 | 
					import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
 | 
				
			||||||
 | 
					import varzia from "@/static/fixed_responses/worldState/varzia.json";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ListedItem {
 | 
					interface ListedItem {
 | 
				
			||||||
    uniqueName: string;
 | 
					    uniqueName: string;
 | 
				
			||||||
@ -36,7 +36,6 @@ interface ListedItem {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ItemLists {
 | 
					interface ItemLists {
 | 
				
			||||||
    archonCrystalUpgrades: Record<string, string>;
 | 
					 | 
				
			||||||
    uniqueLevelCaps: Record<string, number>;
 | 
					    uniqueLevelCaps: Record<string, number>;
 | 
				
			||||||
    Suits: ListedItem[];
 | 
					    Suits: ListedItem[];
 | 
				
			||||||
    LongGuns: ListedItem[];
 | 
					    LongGuns: ListedItem[];
 | 
				
			||||||
@ -57,6 +56,8 @@ interface ItemLists {
 | 
				
			|||||||
    EvolutionProgress: ListedItem[];
 | 
					    EvolutionProgress: ListedItem[];
 | 
				
			||||||
    mods: ListedItem[];
 | 
					    mods: ListedItem[];
 | 
				
			||||||
    Boosters: ListedItem[];
 | 
					    Boosters: ListedItem[];
 | 
				
			||||||
 | 
					    VarziaOffers: ListedItem[];
 | 
				
			||||||
 | 
					    //circuitGameModes: ListedItem[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
					const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
				
			||||||
@ -66,10 +67,13 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
				
			|||||||
    VPQ_PLATINUM: " [Exceptional]"
 | 
					    VPQ_PLATINUM: " [Exceptional]"
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*const toTitleCase = (str: string): string => {
 | 
				
			||||||
 | 
					    return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase());
 | 
				
			||||||
 | 
					};*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getItemListsController: RequestHandler = (req, response) => {
 | 
					const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			||||||
    const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
 | 
					    const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
 | 
				
			||||||
    const res: ItemLists = {
 | 
					    const res: ItemLists = {
 | 
				
			||||||
        archonCrystalUpgrades,
 | 
					 | 
				
			||||||
        uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
 | 
					        uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
 | 
				
			||||||
        Suits: [],
 | 
					        Suits: [],
 | 
				
			||||||
        LongGuns: [],
 | 
					        LongGuns: [],
 | 
				
			||||||
@ -89,7 +93,38 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
        KubrowPets: [],
 | 
					        KubrowPets: [],
 | 
				
			||||||
        EvolutionProgress: [],
 | 
					        EvolutionProgress: [],
 | 
				
			||||||
        mods: [],
 | 
					        mods: [],
 | 
				
			||||||
        Boosters: []
 | 
					        Boosters: [],
 | 
				
			||||||
 | 
					        VarziaOffers: []
 | 
				
			||||||
 | 
					        /*circuitGameModes: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                uniqueName: "Survival",
 | 
				
			||||||
 | 
					                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Survival", lang))
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                uniqueName: "VoidFlood",
 | 
				
			||||||
 | 
					                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Corruption", lang))
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                uniqueName: "Excavation",
 | 
				
			||||||
 | 
					                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Excavation", lang))
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                uniqueName: "Defense",
 | 
				
			||||||
 | 
					                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Defense", lang))
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                uniqueName: "Exterminate",
 | 
				
			||||||
 | 
					                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Exterminate", lang))
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                uniqueName: "Assassination",
 | 
				
			||||||
 | 
					                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Assassination", lang))
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                uniqueName: "Alchemy",
 | 
				
			||||||
 | 
					                name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Alchemy", lang))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ]*/
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
 | 
					    for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
 | 
				
			||||||
        res[item.productCategory].push({
 | 
					        res[item.productCategory].push({
 | 
				
			||||||
@ -306,6 +341,13 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const item of Object.values(varzia.primeDualPacks)) {
 | 
				
			||||||
 | 
					        res.VarziaOffers.push({
 | 
				
			||||||
 | 
					            uniqueName: item.ItemType,
 | 
				
			||||||
 | 
					            name: getString(getItemName(item.ItemType) || "", lang)
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response.json(res);
 | 
					    response.json(res);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -128,7 +128,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                    await completeQuest(inventory, questKey.ItemType);
 | 
					                    await completeQuest(inventory, questKey.ItemType);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    const progress = {
 | 
					                    const progress = {
 | 
				
			||||||
                        c: questManifest.chainStages![currentStage].key ? -1 : 0,
 | 
					                        c: 0,
 | 
				
			||||||
                        i: false,
 | 
					                        i: false,
 | 
				
			||||||
                        m: false,
 | 
					                        m: false,
 | 
				
			||||||
                        b: []
 | 
					                        b: []
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ export const popArchonCrystalUpgradeController: RequestHandler = async (req, res
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        res.end();
 | 
					        res.end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.status(400).end();
 | 
					    res.status(400).end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ export const pushArchonCrystalUpgradeController: RequestHandler = async (req, re
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.end();
 | 
					            res.end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.status(400).end();
 | 
					    res.status(400).end();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountForRequest, isAdministrator, isNameTaken } from "@/src/services/loginService";
 | 
					import { getAccountForRequest, isAdministrator, isNameTaken } from "@/src/services/loginService";
 | 
				
			||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
import { saveConfig } from "@/src/services/configWatcherService";
 | 
					import { saveConfig } from "@/src/services/configWriterService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const renameAccountController: RequestHandler = async (req, res) => {
 | 
					export const renameAccountController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const account = await getAccountForRequest(req);
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
 | 
				
			|||||||
@ -23,9 +23,9 @@ export const setBoosterController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        res.status(400).send("Invalid ItemType provided.");
 | 
					        res.status(400).send("Invalid ItemType provided.");
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const now = Math.floor(Date.now() / 1000);
 | 
					    const now = Math.trunc(Date.now() / 1000);
 | 
				
			||||||
    for (const { ItemType, ExpiryDate } of requests) {
 | 
					    for (const { ItemType, ExpiryDate } of requests) {
 | 
				
			||||||
        if (ExpiryDate < now) {
 | 
					        if (ExpiryDate <= now) {
 | 
				
			||||||
            // remove expired boosters
 | 
					            // remove expired boosters
 | 
				
			||||||
            const index = boosters.findIndex(item => item.ItemType === ItemType);
 | 
					            const index = boosters.findIndex(item => item.ItemType === ItemType);
 | 
				
			||||||
            if (index !== -1) {
 | 
					            if (index !== -1) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +0,0 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					 | 
				
			||||||
import { updateConfig } from "@/src/services/configWatcherService";
 | 
					 | 
				
			||||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const updateConfigDataController: RequestHandler = async (req, res) => {
 | 
					 | 
				
			||||||
    const account = await getAccountForRequest(req);
 | 
					 | 
				
			||||||
    if (isAdministrator(account)) {
 | 
					 | 
				
			||||||
        await updateConfig(String(req.body));
 | 
					 | 
				
			||||||
        res.end();
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        res.status(401).end();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { updateConfigDataController };
 | 
					 | 
				
			||||||
							
								
								
									
										39
									
								
								src/controllers/custom/updateFingerprintController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/controllers/custom/updateFingerprintController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					import { getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { WeaponTypeInternal } from "@/src/services/itemDataService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const updateFingerprintController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const request = req.body as IUpdateFingerPrintRequest;
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, request.category);
 | 
				
			||||||
 | 
					    const item = inventory[request.category].id(request.oid);
 | 
				
			||||||
 | 
					    if (item) {
 | 
				
			||||||
 | 
					        if (request.action == "set" && request.upgradeFingerprint.buffs[0].Tag) {
 | 
				
			||||||
 | 
					            const newUpgradeFingerprint = request.upgradeFingerprint;
 | 
				
			||||||
 | 
					            if (!newUpgradeFingerprint.compact) newUpgradeFingerprint.compact = item.ItemType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            item.UpgradeType = request.upgradeType;
 | 
				
			||||||
 | 
					            item.UpgradeFingerprint = JSON.stringify(newUpgradeFingerprint);
 | 
				
			||||||
 | 
					        } else if (request.action == "remove") {
 | 
				
			||||||
 | 
					            item.UpgradeFingerprint = undefined;
 | 
				
			||||||
 | 
					            item.UpgradeType = undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IUpdateFingerPrintRequest {
 | 
				
			||||||
 | 
					    category: WeaponTypeInternal;
 | 
				
			||||||
 | 
					    oid: string;
 | 
				
			||||||
 | 
					    action: "set" | "remove";
 | 
				
			||||||
 | 
					    upgradeType: string;
 | 
				
			||||||
 | 
					    upgradeFingerprint: {
 | 
				
			||||||
 | 
					        compact?: string;
 | 
				
			||||||
 | 
					        buffs: {
 | 
				
			||||||
 | 
					            Tag: string;
 | 
				
			||||||
 | 
					            Value: number;
 | 
				
			||||||
 | 
					        }[];
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/controllers/custom/webuiFileChangeDetectedController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/controllers/custom/webuiFileChangeDetectedController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					import { args } from "@/src/helpers/commandLineArguments";
 | 
				
			||||||
 | 
					import { sendWsBroadcast } from "@/src/services/wsService";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const webuiFileChangeDetectedController: RequestHandler = (req, res) => {
 | 
				
			||||||
 | 
					    if (args.dev && args.secret && req.query.secret == args.secret) {
 | 
				
			||||||
 | 
					        sendWsBroadcast({ reload: true });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -6,13 +6,11 @@ import { Account } from "@/src/models/loginModel";
 | 
				
			|||||||
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
 | 
					import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
 | 
				
			||||||
import { allDailyAffiliationKeys } from "@/src/services/inventoryService";
 | 
					import { allDailyAffiliationKeys } from "@/src/services/inventoryService";
 | 
				
			||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
 | 
					import { IMongoDate, IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    IAffiliation,
 | 
					    IAffiliation,
 | 
				
			||||||
    IAlignment,
 | 
					    IAlignment,
 | 
				
			||||||
    IChallengeProgress,
 | 
					    IChallengeProgress,
 | 
				
			||||||
    IDailyAffiliations,
 | 
					    IDailyAffiliations,
 | 
				
			||||||
    ILoadoutConfigClient,
 | 
					 | 
				
			||||||
    IMission,
 | 
					    IMission,
 | 
				
			||||||
    IPlayerSkills,
 | 
					    IPlayerSkills,
 | 
				
			||||||
    ITypeXPItem
 | 
					    ITypeXPItem
 | 
				
			||||||
@ -23,6 +21,8 @@ import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			|||||||
import { IStatsClient } from "@/src/types/statTypes";
 | 
					import { IStatsClient } from "@/src/types/statTypes";
 | 
				
			||||||
import { toStoreItem } from "@/src/services/itemDataService";
 | 
					import { toStoreItem } from "@/src/services/itemDataService";
 | 
				
			||||||
import { FlattenMaps } from "mongoose";
 | 
					import { FlattenMaps } from "mongoose";
 | 
				
			||||||
 | 
					import { IEquipmentClient } from "@/src/types/equipmentTypes";
 | 
				
			||||||
 | 
					import { ILoadoutConfigClient } from "@/src/types/saveLoadoutTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
 | 
					const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
 | 
				
			||||||
    const account = await Account.findById(playerId, "DisplayName");
 | 
					    const account = await Account.findById(playerId, "DisplayName");
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,19 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import { getWorldState } from "@/src/services/worldStateService";
 | 
					import { getWorldState, populateDailyDeal, populateFissures } from "@/src/services/worldStateService";
 | 
				
			||||||
 | 
					import { version_compare } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const worldStateController: RequestHandler = (req, res) => {
 | 
					export const worldStateController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    res.json(getWorldState(req.query.buildLabel as string | undefined));
 | 
					    const buildLabel = req.query.buildLabel as string | undefined;
 | 
				
			||||||
 | 
					    const worldState = getWorldState(buildLabel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const populatePromises = [populateDailyDeal(worldState)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Omitting void fissures for versions prior to Dante Unbound to avoid script errors.
 | 
				
			||||||
 | 
					    if (!buildLabel || version_compare(buildLabel, "2024.03.24.20.00") >= 0) {
 | 
				
			||||||
 | 
					        populatePromises.push(populateFissures(worldState));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await Promise.all(populatePromises);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.json(worldState);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										23
									
								
								src/helpers/commandLineArguments.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/helpers/commandLineArguments.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					interface IArguments {
 | 
				
			||||||
 | 
					    configPath?: string;
 | 
				
			||||||
 | 
					    dev?: boolean;
 | 
				
			||||||
 | 
					    secret?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const args: IArguments = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for (let i = 2; i < process.argv.length; ) {
 | 
				
			||||||
 | 
					    switch (process.argv[i++]) {
 | 
				
			||||||
 | 
					        case "--configPath":
 | 
				
			||||||
 | 
					            args.configPath = process.argv[i++];
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "--dev":
 | 
				
			||||||
 | 
					            args.dev = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "--secret":
 | 
				
			||||||
 | 
					            args.secret = process.argv[i++];
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { IAccountCreation } from "@/src/types/customTypes";
 | 
					import { IAccountCreation } from "@/src/types/customTypes";
 | 
				
			||||||
import { IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
 | 
					import { IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
 | 
				
			||||||
import crypto from "crypto";
 | 
					import crypto from "crypto";
 | 
				
			||||||
import { isString, parseEmail, parseString } from "../general";
 | 
					import { isString, parseEmail, parseString } from "@/src/helpers/general";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getWhirlpoolHash = (rawPassword: string): string => {
 | 
					const getWhirlpoolHash = (rawPassword: string): string => {
 | 
				
			||||||
    const whirlpool = crypto.createHash("whirlpool");
 | 
					    const whirlpool = crypto.createHash("whirlpool");
 | 
				
			||||||
 | 
				
			|||||||
@ -9,11 +9,11 @@ export const isEmptyObject = (obj: object): boolean => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isString = (text: unknown): text is string => {
 | 
					export const isString = (text: unknown): text is string => {
 | 
				
			||||||
    return typeof text === "string" || text instanceof String;
 | 
					    return typeof text === "string" || text instanceof String;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseString = (data: unknown): string => {
 | 
					export const parseString = (data: unknown): string => {
 | 
				
			||||||
    if (!isString(data)) {
 | 
					    if (!isString(data)) {
 | 
				
			||||||
        throw new Error("data is not a string");
 | 
					        throw new Error("data is not a string");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -21,11 +21,11 @@ const parseString = (data: unknown): string => {
 | 
				
			|||||||
    return data;
 | 
					    return data;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isNumber = (number: unknown): number is number => {
 | 
					export const isNumber = (number: unknown): number is number => {
 | 
				
			||||||
    return typeof number === "number" && !isNaN(number);
 | 
					    return typeof number === "number" && !isNaN(number);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseNumber = (data: unknown): number => {
 | 
					export const parseNumber = (data: unknown): number => {
 | 
				
			||||||
    if (!isNumber(data)) {
 | 
					    if (!isNumber(data)) {
 | 
				
			||||||
        throw new Error("data is not a number");
 | 
					        throw new Error("data is not a number");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -33,11 +33,11 @@ const parseNumber = (data: unknown): number => {
 | 
				
			|||||||
    return Number(data);
 | 
					    return Number(data);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isDate = (date: string): boolean => {
 | 
					export const isDate = (date: string): boolean => {
 | 
				
			||||||
    return Date.parse(date) != 0;
 | 
					    return Date.parse(date) != 0;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseDateNumber = (date: unknown): string => {
 | 
					export const parseDateNumber = (date: unknown): string => {
 | 
				
			||||||
    if (!isString(date) || !isDate(date)) {
 | 
					    if (!isString(date) || !isDate(date)) {
 | 
				
			||||||
        throw new Error("date could not be parsed");
 | 
					        throw new Error("date could not be parsed");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -45,18 +45,18 @@ const parseDateNumber = (date: unknown): string => {
 | 
				
			|||||||
    return date;
 | 
					    return date;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseEmail = (email: unknown): string => {
 | 
					export const parseEmail = (email: unknown): string => {
 | 
				
			||||||
    if (!isString(email)) {
 | 
					    if (!isString(email)) {
 | 
				
			||||||
        throw new Error("incorrect email");
 | 
					        throw new Error("incorrect email");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return email;
 | 
					    return email;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isBoolean = (booleanCandidate: unknown): booleanCandidate is boolean => {
 | 
					export const isBoolean = (booleanCandidate: unknown): booleanCandidate is boolean => {
 | 
				
			||||||
    return typeof booleanCandidate === "boolean";
 | 
					    return typeof booleanCandidate === "boolean";
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const parseBoolean = (booleanCandidate: unknown): boolean => {
 | 
					export const parseBoolean = (booleanCandidate: unknown): boolean => {
 | 
				
			||||||
    if (!isBoolean(booleanCandidate)) {
 | 
					    if (!isBoolean(booleanCandidate)) {
 | 
				
			||||||
        throw new Error("argument was not a boolean");
 | 
					        throw new Error("argument was not a boolean");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -70,5 +70,3 @@ export const isObject = (objectCandidate: unknown): objectCandidate is Record<st
 | 
				
			|||||||
        !Array.isArray(objectCandidate)
 | 
					        !Array.isArray(objectCandidate)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { isString, isNumber, parseString, parseNumber, parseDateNumber, parseBoolean, parseEmail };
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -51,6 +51,11 @@ export const fromMongoDate = (date: IMongoDate): Date => {
 | 
				
			|||||||
    return new Date(parseInt(date.$date.$numberLong));
 | 
					    return new Date(parseInt(date.$date.$numberLong));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TTraitsPool = Record<
 | 
				
			||||||
 | 
					    "Colors" | "EyeColors" | "FurPatterns" | "BodyTypes" | "Heads" | "Tails",
 | 
				
			||||||
 | 
					    { type: string; rarity: TRarity }[]
 | 
				
			||||||
 | 
					>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const kubrowWeights: Record<TRarity, number> = {
 | 
					export const kubrowWeights: Record<TRarity, number> = {
 | 
				
			||||||
    COMMON: 6,
 | 
					    COMMON: 6,
 | 
				
			||||||
    UNCOMMON: 4,
 | 
					    UNCOMMON: 4,
 | 
				
			||||||
@ -65,126 +70,126 @@ export const kubrowFurPatternsWeights: Record<TRarity, number> = {
 | 
				
			|||||||
    LEGENDARY: 1
 | 
					    LEGENDARY: 1
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const catbrowDetails = {
 | 
					export const catbrowDetails: TTraitsPool = {
 | 
				
			||||||
    Colors: [
 | 
					    Colors: [
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" as TRarity }
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    EyeColors: [
 | 
					    EyeColors: [
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" as TRarity }
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" as TRarity }],
 | 
					    FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" }],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BodyTypes: [
 | 
					    BodyTypes: [
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" as TRarity }
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Heads: [
 | 
					    Heads: [
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" as TRarity }
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Tails: [
 | 
					    Tails: [
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" as TRarity }
 | 
					        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const kubrowDetails = {
 | 
					export const kubrowDetails: TTraitsPool = {
 | 
				
			||||||
    Colors: [
 | 
					    Colors: [
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" as TRarity }
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    EyeColors: [
 | 
					    EyeColors: [
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" as TRarity }
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FurPatterns: [
 | 
					    FurPatterns: [
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" as TRarity }
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BodyTypes: [
 | 
					    BodyTypes: [
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" as TRarity },
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" },
 | 
				
			||||||
        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" as TRarity }
 | 
					        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Heads: [],
 | 
					    Heads: [],
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { TEquipmentKey } from "../types/inventoryTypes/inventoryTypes";
 | 
					import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const modularWeaponTypes: Record<string, TEquipmentKey> = {
 | 
					export const modularWeaponTypes: Record<string, TEquipmentKey> = {
 | 
				
			||||||
    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns",
 | 
					    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,9 @@
 | 
				
			|||||||
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
 | 
					import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
 | 
				
			||||||
import { IInfNode, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { IInfNode, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
 | 
					import { generateRewardSeed, getRewardAtPercentage, SRng } from "@/src/services/rngService";
 | 
				
			||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import { logger } from "../utils/logger";
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { IOid } from "../types/commonTypes";
 | 
					import { isArchwingMission } from "@/src/services/worldStateService";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					 | 
				
			||||||
import { addMods, generateRewardSeed } from "../services/inventoryService";
 | 
					 | 
				
			||||||
import { isArchwingMission } from "../services/worldStateService";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TInnateDamageTag =
 | 
					type TInnateDamageTag =
 | 
				
			||||||
    | "InnateElectricityDamage"
 | 
					    | "InnateElectricityDamage"
 | 
				
			||||||
@ -237,7 +234,7 @@ export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFacti
 | 
				
			|||||||
    return passcode;
 | 
					    return passcode;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const requiemMods: readonly string[] = [
 | 
					/*const requiemMods: readonly string[] = [
 | 
				
			||||||
    "/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
 | 
					    "/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
 | 
				
			||||||
    "/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
 | 
					    "/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
 | 
				
			||||||
    "/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
 | 
					    "/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
 | 
				
			||||||
@ -246,9 +243,9 @@ const requiemMods: readonly string[] = [
 | 
				
			|||||||
    "/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
 | 
					    "/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
 | 
				
			||||||
    "/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
 | 
					    "/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
 | 
				
			||||||
    "/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod"
 | 
					    "/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod"
 | 
				
			||||||
];
 | 
					];*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const antivirusMods: readonly string[] = [
 | 
					export const antivirusMods: readonly string[] = [
 | 
				
			||||||
    "/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
 | 
					    "/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
 | 
				
			||||||
    "/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
 | 
					    "/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
 | 
				
			||||||
    "/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
 | 
					    "/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
 | 
				
			||||||
@ -259,12 +256,12 @@ const antivirusMods: readonly string[] = [
 | 
				
			|||||||
    "/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
 | 
					    "/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): string[] => {
 | 
					/*export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): string[] => {
 | 
				
			||||||
    const passcode = getNemesisPasscode(nemesis);
 | 
					    const passcode = getNemesisPasscode(nemesis);
 | 
				
			||||||
    return nemesis.Faction == "FC_INFESTATION"
 | 
					    return nemesis.Faction == "FC_INFESTATION"
 | 
				
			||||||
        ? passcode.map(i => antivirusMods[i])
 | 
					        ? passcode.map(i => antivirusMods[i])
 | 
				
			||||||
        : passcode.map(i => requiemMods[i]);
 | 
					        : passcode.map(i => requiemMods[i]);
 | 
				
			||||||
};
 | 
					};*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Symbols; 0-7 are the normal requiem mods.
 | 
					// Symbols; 0-7 are the normal requiem mods.
 | 
				
			||||||
export const GUESS_NONE = 8;
 | 
					export const GUESS_NONE = 8;
 | 
				
			||||||
@ -343,54 +340,24 @@ export const getKnifeUpgrade = (
 | 
				
			|||||||
    throw new Error(`${type} does not seem to be installed on parazon?!`);
 | 
					    throw new Error(`${type} does not seem to be installed on parazon?!`);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const consumeModCharge = (
 | 
					export const parseUpgrade = (
 | 
				
			||||||
    response: IKnifeResponse,
 | 
					 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    upgrade: { ItemId: IOid; ItemType: string },
 | 
					    str: string
 | 
				
			||||||
    dataknifeUpgrades: string[]
 | 
					): { ItemId: IOid; ItemType: string } => {
 | 
				
			||||||
): void => {
 | 
					    if (str.length == 24) {
 | 
				
			||||||
    response.UpgradeIds ??= [];
 | 
					        const upgrade = inventory.Upgrades.id(str);
 | 
				
			||||||
    response.UpgradeTypes ??= [];
 | 
					        if (upgrade) {
 | 
				
			||||||
    response.UpgradeFingerprints ??= [];
 | 
					            return {
 | 
				
			||||||
    response.UpgradeNew ??= [];
 | 
					                ItemId: { $oid: str },
 | 
				
			||||||
    response.HasKnife = true;
 | 
					                ItemType: upgrade.ItemType
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
    if (upgrade.ItemId.$oid != "000000000000000000000000") {
 | 
					 | 
				
			||||||
        const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
 | 
					 | 
				
			||||||
        const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
 | 
					 | 
				
			||||||
        fingerprint.lvl += 1;
 | 
					 | 
				
			||||||
        dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        response.UpgradeIds.push(upgrade.ItemId.$oid);
 | 
					 | 
				
			||||||
        response.UpgradeTypes.push(upgrade.ItemType);
 | 
					 | 
				
			||||||
        response.UpgradeFingerprints.push(fingerprint);
 | 
					 | 
				
			||||||
        response.UpgradeNew.push(false);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        const id = new Types.ObjectId();
 | 
					 | 
				
			||||||
        inventory.Upgrades.push({
 | 
					 | 
				
			||||||
            _id: id,
 | 
					 | 
				
			||||||
            ItemType: upgrade.ItemType,
 | 
					 | 
				
			||||||
            UpgradeFingerprint: `{"lvl":1}`
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        addMods(inventory, [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ItemType: upgrade.ItemType,
 | 
					 | 
				
			||||||
                ItemCount: -1
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType);
 | 
					 | 
				
			||||||
        if (dataknifeRawUpgradeIndex != -1) {
 | 
					 | 
				
			||||||
            dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString();
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            logger.warn(`${upgrade.ItemType} not found in dataknife config`);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error(`Could not resolve oid ${str}`);
 | 
				
			||||||
        response.UpgradeIds.push(id.toString());
 | 
					    } else {
 | 
				
			||||||
        response.UpgradeTypes.push(upgrade.ItemType);
 | 
					        return {
 | 
				
			||||||
        response.UpgradeFingerprints.push({ lvl: 1 });
 | 
					            ItemId: { $oid: "000000000000000000000000" },
 | 
				
			||||||
        response.UpgradeNew.push(true);
 | 
					            ItemType: str
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,4 @@
 | 
				
			|||||||
import path from "path";
 | 
					import path from "path";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const rootDir = path.join(__dirname, "../..");
 | 
					export const rootDir = path.join(__dirname, "../..");
 | 
				
			||||||
export const isDev = path.basename(rootDir) != "build";
 | 
					export const repoDir = path.basename(rootDir) != "build" ? rootDir : path.join(rootDir, "..");
 | 
				
			||||||
export const repoDir = isDev ? rootDir : path.join(rootDir, "..");
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,8 @@ import { getRandomWeightedReward, IRngResult } from "@/src/services/rngService";
 | 
				
			|||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { addMiscItems, combineInventoryChanges } from "@/src/services/inventoryService";
 | 
					import { addMiscItems, combineInventoryChanges } from "@/src/services/inventoryService";
 | 
				
			||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
					import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
import { IInventoryChanges } from "../types/purchaseTypes";
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const crackRelic = async (
 | 
					export const crackRelic = async (
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
@ -13,12 +14,25 @@ export const crackRelic = async (
 | 
				
			|||||||
    inventoryChanges: IInventoryChanges = {}
 | 
					    inventoryChanges: IInventoryChanges = {}
 | 
				
			||||||
): Promise<IRngResult> => {
 | 
					): Promise<IRngResult> => {
 | 
				
			||||||
    const relic = ExportRelics[participant.VoidProjection];
 | 
					    const relic = ExportRelics[participant.VoidProjection];
 | 
				
			||||||
    const weights = refinementToWeights[relic.quality];
 | 
					    let weights = refinementToWeights[relic.quality];
 | 
				
			||||||
 | 
					    if (relic.quality == "VPQ_SILVER" && config.exceptionalRelicsAlwaysGiveBronzeReward) {
 | 
				
			||||||
 | 
					        weights = { COMMON: 1, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 };
 | 
				
			||||||
 | 
					    } else if (relic.quality == "VPQ_GOLD" && config.flawlessRelicsAlwaysGiveSilverReward) {
 | 
				
			||||||
 | 
					        weights = { COMMON: 0, UNCOMMON: 1, RARE: 0, LEGENDARY: 0 };
 | 
				
			||||||
 | 
					    } else if (relic.quality == "VPQ_PLATINUM" && config.radiantRelicsAlwaysGiveGoldReward) {
 | 
				
			||||||
 | 
					        weights = { COMMON: 0, UNCOMMON: 0, RARE: 1, LEGENDARY: 0 };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
 | 
					    logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
 | 
				
			||||||
    const reward = getRandomWeightedReward(
 | 
					    let reward = getRandomWeightedReward(
 | 
				
			||||||
        ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
 | 
					        ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
 | 
				
			||||||
        weights
 | 
					        weights
 | 
				
			||||||
    )!;
 | 
					    )!;
 | 
				
			||||||
 | 
					    if (config.relicRewardItemCountMultiplier !== undefined && (config.relicRewardItemCountMultiplier ?? 1) != 1) {
 | 
				
			||||||
 | 
					        reward = {
 | 
				
			||||||
 | 
					            ...reward,
 | 
				
			||||||
 | 
					            itemCount: reward.itemCount * config.relicRewardItemCountMultiplier
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    logger.debug(`relic rolled`, reward);
 | 
					    logger.debug(`relic rolled`, reward);
 | 
				
			||||||
    participant.Reward = reward.type;
 | 
					    participant.Reward = reward.type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { IUpgrade } from "warframe-public-export-plus";
 | 
					import { IUpgrade } from "warframe-public-export-plus";
 | 
				
			||||||
import { getRandomElement, getRandomInt, getRandomReward } from "../services/rngService";
 | 
					import { getRandomElement, getRandomInt, getRandomReward } from "@/src/services/rngService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint;
 | 
					export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -54,3 +54,9 @@ export const regexEscape = (str: string): string => {
 | 
				
			|||||||
    str = str.split("}").join("\\}");
 | 
					    str = str.split("}").join("\\}");
 | 
				
			||||||
    return str;
 | 
					    return str;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addString = (arr: string[], str: string): void => {
 | 
				
			||||||
 | 
					    if (arr.indexOf(str) == -1) {
 | 
				
			||||||
 | 
					        arr.push(str);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -10,3 +10,14 @@ export const getMaxStanding = (syndicate: ISyndicate, title: number): number =>
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return syndicate.titles.find(x => x.level == title)!.maxStanding;
 | 
					    return syndicate.titles.find(x => x.level == title)!.maxStanding;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getMinStanding = (syndicate: ISyndicate, title: number): number => {
 | 
				
			||||||
 | 
					    if (!syndicate.titles) {
 | 
				
			||||||
 | 
					        // LibrarySyndicate
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (title == 0) {
 | 
				
			||||||
 | 
					        return syndicate.titles.find(x => x.level == -1)!.maxStanding;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return syndicate.titles.find(x => x.level == title)!.minStanding;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										21
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								src/index.ts
									
									
									
									
									
								
							@ -1,9 +1,14 @@
 | 
				
			|||||||
// First, init config.
 | 
					// First, init config.
 | 
				
			||||||
import { config, loadConfig } from "@/src/services/configService";
 | 
					import { config, configPath, loadConfig } from "@/src/services/configService";
 | 
				
			||||||
 | 
					import fs from "fs";
 | 
				
			||||||
try {
 | 
					try {
 | 
				
			||||||
    loadConfig();
 | 
					    loadConfig();
 | 
				
			||||||
} catch (e) {
 | 
					} catch (e) {
 | 
				
			||||||
    console.log("ERROR: Failed to load config.json. You can copy config.json.example to create your config.json.");
 | 
					    if (fs.existsSync("config.json")) {
 | 
				
			||||||
 | 
					        console.log("Failed to load " + configPath + ": " + (e as Error).message);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        console.log("Failed to load " + configPath + ". You can copy config.json.example to create your config file.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    process.exit(1);
 | 
					    process.exit(1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -14,9 +19,10 @@ logger.info("Starting up...");
 | 
				
			|||||||
// Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP.
 | 
					// Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP.
 | 
				
			||||||
import mongoose from "mongoose";
 | 
					import mongoose from "mongoose";
 | 
				
			||||||
import { JSONStringify } from "json-with-bigint";
 | 
					import { JSONStringify } from "json-with-bigint";
 | 
				
			||||||
import { startWebServer } from "./services/webService";
 | 
					import { startWebServer } from "@/src/services/webService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { validateConfig } from "@/src/services/configWatcherService";
 | 
					import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
 | 
				
			||||||
 | 
					import { updateWorldStateCollections } from "@/src/services/worldStateService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Patch JSON.stringify to work flawlessly with Bigints.
 | 
					// Patch JSON.stringify to work flawlessly with Bigints.
 | 
				
			||||||
JSON.stringify = JSONStringify;
 | 
					JSON.stringify = JSONStringify;
 | 
				
			||||||
@ -27,7 +33,14 @@ mongoose
 | 
				
			|||||||
    .connect(config.mongodbUrl)
 | 
					    .connect(config.mongodbUrl)
 | 
				
			||||||
    .then(() => {
 | 
					    .then(() => {
 | 
				
			||||||
        logger.info("Connected to MongoDB");
 | 
					        logger.info("Connected to MongoDB");
 | 
				
			||||||
 | 
					        syncConfigWithDatabase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        startWebServer();
 | 
					        startWebServer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void updateWorldStateCollections();
 | 
				
			||||||
 | 
					        setInterval(() => {
 | 
				
			||||||
 | 
					            void updateWorldStateCollections();
 | 
				
			||||||
 | 
					        }, 60_000);
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .catch(error => {
 | 
					    .catch(error => {
 | 
				
			||||||
        if (error instanceof Error) {
 | 
					        if (error instanceof Error) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { NextFunction, Request, Response } from "express";
 | 
					import { NextFunction, Request, Response } from "express";
 | 
				
			||||||
import { logger } from "../utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => {
 | 
					export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => {
 | 
				
			||||||
    if (err.message == "Invalid accountId-nonce pair") {
 | 
					    if (err.message == "Invalid accountId-nonce pair") {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										25
									
								
								src/models/commonModel.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/models/commonModel.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import { Schema } from "mongoose";
 | 
				
			||||||
 | 
					import { IColor, IShipCustomization } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const colorSchema = new Schema<IColor>(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        t0: Number,
 | 
				
			||||||
 | 
					        t1: Number,
 | 
				
			||||||
 | 
					        t2: Number,
 | 
				
			||||||
 | 
					        t3: Number,
 | 
				
			||||||
 | 
					        en: Number,
 | 
				
			||||||
 | 
					        e1: Number,
 | 
				
			||||||
 | 
					        m0: Number,
 | 
				
			||||||
 | 
					        m1: Number
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    { _id: false }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const shipCustomizationSchema = new Schema<IShipCustomization>(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        SkinFlavourItem: String,
 | 
				
			||||||
 | 
					        Colors: colorSchema,
 | 
				
			||||||
 | 
					        ShipAttachments: { HOOD_ORNAMENT: String }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    { _id: false }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
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