merge upstream
This commit is contained in:
commit
862465ea3c
18
.eslintrc
18
.eslintrc
@ -11,17 +11,17 @@
|
|||||||
"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",
|
||||||
|
3
.github/workflows/build.yml
vendored
3
.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:
|
||||||
|
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,31 @@
|
|||||||
"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
|
||||||
|
},
|
||||||
|
"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
|
|
||||||
|
731
package-lock.json
generated
731
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@ -5,9 +5,16 @@
|
|||||||
"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 .",
|
||||||
@ -18,6 +25,10 @@
|
|||||||
"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 +36,19 @@
|
|||||||
"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.76",
|
||||||
"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-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) {}
|
||||||
|
});
|
@ -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) {
|
||||||
|
@ -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,7 +13,8 @@ import {
|
|||||||
addItem,
|
addItem,
|
||||||
addRecipes,
|
addRecipes,
|
||||||
occupySlot,
|
occupySlot,
|
||||||
combineInventoryChanges
|
combineInventoryChanges,
|
||||||
|
addKubrowPetPrint
|
||||||
} 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 { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
@ -119,6 +120,9 @@ 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 = {
|
InventoryChanges = {
|
||||||
...InventoryChanges,
|
...InventoryChanges,
|
||||||
|
@ -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 { checkCalendarChallengeCompletion, 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;
|
||||||
|
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: inventoryChanges,
|
InventoryChanges: inventoryChanges,
|
||||||
|
@ -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];
|
@ -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
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,7 +43,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 +78,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 +96,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,
|
||||||
@ -123,7 +123,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
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 +168,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;
|
||||||
|
@ -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
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -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 {
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -6,29 +6,28 @@ 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, EquipmentFeatures } 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,
|
||||||
cleanupInventory,
|
cleanupInventory,
|
||||||
createLibraryDailyTask,
|
createLibraryDailyTask,
|
||||||
generateRewardSeed
|
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";
|
||||||
|
|
||||||
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 +41,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 +53,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 +90,84 @@ 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 = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inventory.CalendarProgress) {
|
||||||
|
const previousYearIteration = inventory.CalendarProgress.Iteration;
|
||||||
|
getCalendarProgress(inventory); // handle year rollover; the client expects to receive an inventory with an up-to-date CalendarProgress
|
||||||
|
|
||||||
|
// 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 +208,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 +249,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 +370,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 +406,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) {
|
||||||
|
@ -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/webService";
|
||||||
|
|
||||||
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/webService";
|
||||||
|
|
||||||
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/webService";
|
||||||
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/webService";
|
||||||
|
|
||||||
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",
|
||||||
|
@ -7,6 +7,7 @@ import { generateRewardSeed, getInventory } from "@/src/services/inventoryServic
|
|||||||
import { getInventoryResponse } from "./inventoryController";
|
import { getInventoryResponse } from "./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/webService";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
**** INPUT ****
|
**** INPUT ****
|
||||||
@ -76,6 +77,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 +88,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 +108,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
|||||||
AffiliationMods,
|
AffiliationMods,
|
||||||
ConquestCompletedMissionsCount
|
ConquestCompletedMissionsCount
|
||||||
} satisfies IMissionInventoryUpdateResponse);
|
} satisfies IMissionInventoryUpdateResponse);
|
||||||
|
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -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/webService";
|
||||||
|
|
||||||
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,5 +1,6 @@
|
|||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
import {
|
import {
|
||||||
|
antivirusMods,
|
||||||
consumeModCharge,
|
consumeModCharge,
|
||||||
decodeNemesisGuess,
|
decodeNemesisGuess,
|
||||||
encodeNemesisGuess,
|
encodeNemesisGuess,
|
||||||
@ -7,13 +8,13 @@ import {
|
|||||||
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 { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||||
@ -134,34 +135,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 +203,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 +306,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 +370,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[];
|
||||||
|
@ -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) {
|
||||||
|
@ -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/webService";
|
||||||
|
|
||||||
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/webService";
|
||||||
|
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 {
|
||||||
|
@ -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);
|
||||||
|
@ -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/webService";
|
||||||
|
|
||||||
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[];
|
||||||
};
|
};
|
||||||
|
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/inventoryTypes/commonInventoryTypes";
|
||||||
|
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[];
|
||||||
|
}
|
@ -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,7 +5,7 @@ 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/inventoryTypes/commonInventoryTypes";
|
||||||
|
|
||||||
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
|
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
|
||||||
@ -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/inventoryTypes/commonInventoryTypes";
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
};
|
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();
|
||||||
|
};
|
44
src/controllers/custom/configController.ts
Normal file
44
src/controllers/custom/configController.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
||||||
|
import { saveConfig } from "@/src/services/configWatcherService";
|
||||||
|
import { sendWsBroadcastExcept } from "@/src/services/webService";
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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,7 +20,6 @@ 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";
|
||||||
|
|
||||||
interface ListedItem {
|
interface ListedItem {
|
||||||
@ -36,7 +35,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 +55,7 @@ interface ItemLists {
|
|||||||
EvolutionProgress: ListedItem[];
|
EvolutionProgress: ListedItem[];
|
||||||
mods: ListedItem[];
|
mods: ListedItem[];
|
||||||
Boosters: ListedItem[];
|
Boosters: ListedItem[];
|
||||||
|
//circuitGameModes: ListedItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
||||||
@ -66,10 +65,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: [],
|
||||||
@ -90,6 +92,36 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
EvolutionProgress: [],
|
EvolutionProgress: [],
|
||||||
mods: [],
|
mods: [],
|
||||||
Boosters: []
|
Boosters: []
|
||||||
|
/*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({
|
||||||
|
@ -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();
|
||||||
|
@ -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/webService";
|
||||||
|
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();
|
||||||
|
};
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -237,7 +237,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 +246,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 +259,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,6 +343,27 @@ 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 parseUpgrade = (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
str: string
|
||||||
|
): { ItemId: IOid; ItemType: string } => {
|
||||||
|
if (str.length == 24) {
|
||||||
|
const upgrade = inventory.Upgrades.id(str);
|
||||||
|
if (upgrade) {
|
||||||
|
return {
|
||||||
|
ItemId: { $oid: str },
|
||||||
|
ItemType: upgrade.ItemType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`Could not resolve oid ${str}`);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
ItemId: { $oid: "000000000000000000000000" },
|
||||||
|
ItemType: str
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const consumeModCharge = (
|
export const consumeModCharge = (
|
||||||
response: IKnifeResponse,
|
response: IKnifeResponse,
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
@ -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, "..");
|
|
||||||
|
@ -6,6 +6,7 @@ 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 "../types/purchaseTypes";
|
||||||
|
import { config } from "../services/configService";
|
||||||
|
|
||||||
export const crackRelic = async (
|
export const crackRelic = async (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
@ -35,7 +36,13 @@ export const crackRelic = async (
|
|||||||
// Give reward
|
// Give reward
|
||||||
combineInventoryChanges(
|
combineInventoryChanges(
|
||||||
inventoryChanges,
|
inventoryChanges,
|
||||||
(await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount)).InventoryChanges
|
(
|
||||||
|
await handleStoreItemAcquisition(
|
||||||
|
reward.type,
|
||||||
|
inventory,
|
||||||
|
reward.itemCount * (config.relicRewardItemCountMultiplier ?? 1)
|
||||||
|
)
|
||||||
|
).InventoryChanges
|
||||||
);
|
);
|
||||||
|
|
||||||
return reward;
|
return reward;
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
19
src/index.ts
19
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,7 +21,8 @@ import mongoose from "mongoose";
|
|||||||
import { JSONStringify } from "json-with-bigint";
|
import { JSONStringify } from "json-with-bigint";
|
||||||
import { startWebServer } from "./services/webService";
|
import { startWebServer } from "./services/webService";
|
||||||
|
|
||||||
import { validateConfig } from "@/src/services/configWatcherService";
|
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
|
||||||
|
import { updateWorldStateCollections } from "./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) {
|
||||||
|
@ -17,22 +17,25 @@ export interface IMessageDatabase extends IMessage {
|
|||||||
ownerId: Types.ObjectId;
|
ownerId: Types.ObjectId;
|
||||||
date: Date; //created at
|
date: Date; //created at
|
||||||
attVisualOnly?: boolean;
|
attVisualOnly?: boolean;
|
||||||
expiry?: Date;
|
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessage {
|
export interface IMessage {
|
||||||
sndr: string;
|
sndr: string;
|
||||||
msg: string;
|
msg: string;
|
||||||
|
cinematic?: string;
|
||||||
sub: string;
|
sub: string;
|
||||||
|
customData?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
highPriority?: boolean;
|
highPriority?: boolean;
|
||||||
lowPrioNewPlayers?: boolean;
|
lowPrioNewPlayers?: boolean;
|
||||||
startDate?: Date;
|
transmission?: string;
|
||||||
endDate?: Date;
|
|
||||||
att?: string[];
|
att?: string[];
|
||||||
countedAtt?: ITypeCount[];
|
countedAtt?: ITypeCount[];
|
||||||
transmission?: string;
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
goalTag?: string;
|
||||||
|
CrossPlatform?: boolean;
|
||||||
arg?: Arg[];
|
arg?: Arg[];
|
||||||
gifts?: IGift[];
|
gifts?: IGift[];
|
||||||
r?: boolean;
|
r?: boolean;
|
||||||
@ -101,13 +104,18 @@ const messageSchema = new Schema<IMessageDatabase>(
|
|||||||
ownerId: Schema.Types.ObjectId,
|
ownerId: Schema.Types.ObjectId,
|
||||||
sndr: String,
|
sndr: String,
|
||||||
msg: String,
|
msg: String,
|
||||||
|
cinematic: String,
|
||||||
sub: String,
|
sub: String,
|
||||||
|
customData: String,
|
||||||
icon: String,
|
icon: String,
|
||||||
highPriority: Boolean,
|
highPriority: Boolean,
|
||||||
lowPrioNewPlayers: Boolean,
|
lowPrioNewPlayers: Boolean,
|
||||||
startDate: Date,
|
startDate: Date,
|
||||||
endDate: Date,
|
endDate: Date,
|
||||||
|
goalTag: String,
|
||||||
|
date: { type: Date, required: true },
|
||||||
r: Boolean,
|
r: Boolean,
|
||||||
|
CrossPlatform: Boolean,
|
||||||
att: { type: [String], default: undefined },
|
att: { type: [String], default: undefined },
|
||||||
gifts: { type: [giftSchema], default: undefined },
|
gifts: { type: [giftSchema], default: undefined },
|
||||||
countedAtt: { type: [typeCountSchema], default: undefined },
|
countedAtt: { type: [typeCountSchema], default: undefined },
|
||||||
@ -128,7 +136,7 @@ const messageSchema = new Schema<IMessageDatabase>(
|
|||||||
declineAction: String,
|
declineAction: String,
|
||||||
hasAccountAction: Boolean
|
hasAccountAction: Boolean
|
||||||
},
|
},
|
||||||
{ timestamps: { createdAt: "date", updatedAt: false }, id: false }
|
{ id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
messageSchema.virtual("messageId").get(function (this: IMessageDatabase) {
|
messageSchema.virtual("messageId").get(function (this: IMessageDatabase) {
|
||||||
@ -151,13 +159,15 @@ messageSchema.set("toJSON", {
|
|||||||
|
|
||||||
if (messageDatabase.startDate && messageDatabase.endDate) {
|
if (messageDatabase.startDate && messageDatabase.endDate) {
|
||||||
messageClient.startDate = toMongoDate(messageDatabase.startDate);
|
messageClient.startDate = toMongoDate(messageDatabase.startDate);
|
||||||
|
|
||||||
messageClient.endDate = toMongoDate(messageDatabase.endDate);
|
messageClient.endDate = toMongoDate(messageDatabase.endDate);
|
||||||
|
} else {
|
||||||
|
delete messageClient.startDate;
|
||||||
|
delete messageClient.endDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
messageSchema.index({ ownerId: 1 });
|
messageSchema.index({ ownerId: 1 });
|
||||||
messageSchema.index({ expiry: 1 }, { expireAfterSeconds: 0 });
|
messageSchema.index({ endDate: 1 }, { expireAfterSeconds: 0 });
|
||||||
|
|
||||||
export const Inbox = model<IMessageDatabase>("Inbox", messageSchema, "inbox");
|
export const Inbox = model<IMessageDatabase>("Inbox", messageSchema, "inbox");
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Document, HydratedDocument, Model, Schema, Types, model } from "mongoose";
|
import { Document, Model, Schema, Types, model } from "mongoose";
|
||||||
import {
|
import {
|
||||||
IFlavourItem,
|
IFlavourItem,
|
||||||
IRawUpgrade,
|
IRawUpgrade,
|
||||||
@ -7,7 +7,6 @@ import {
|
|||||||
IBooster,
|
IBooster,
|
||||||
IInventoryClient,
|
IInventoryClient,
|
||||||
ISlots,
|
ISlots,
|
||||||
IMailboxDatabase,
|
|
||||||
IDuviriInfo,
|
IDuviriInfo,
|
||||||
IPendingRecipeDatabase,
|
IPendingRecipeDatabase,
|
||||||
IPendingRecipeClient,
|
IPendingRecipeClient,
|
||||||
@ -54,7 +53,6 @@ import {
|
|||||||
IUpgradeDatabase,
|
IUpgradeDatabase,
|
||||||
ICrewShipMemberDatabase,
|
ICrewShipMemberDatabase,
|
||||||
ICrewShipMemberClient,
|
ICrewShipMemberClient,
|
||||||
IMailboxClient,
|
|
||||||
TEquipmentKey,
|
TEquipmentKey,
|
||||||
equipmentKeys,
|
equipmentKeys,
|
||||||
IKubrowPetDetailsDatabase,
|
IKubrowPetDetailsDatabase,
|
||||||
@ -93,13 +91,17 @@ import {
|
|||||||
ICrewMemberSkillEfficiency,
|
ICrewMemberSkillEfficiency,
|
||||||
ICrewMemberDatabase,
|
ICrewMemberDatabase,
|
||||||
ICrewMemberClient,
|
ICrewMemberClient,
|
||||||
ISortieRewardAttenuation,
|
IRewardAttenuation,
|
||||||
IInvasionProgressDatabase,
|
IInvasionProgressDatabase,
|
||||||
IInvasionProgressClient,
|
IInvasionProgressClient,
|
||||||
IAccolades,
|
IAccolades,
|
||||||
IHubNpcCustomization,
|
IHubNpcCustomization,
|
||||||
ILotusCustomization,
|
ILotusCustomization,
|
||||||
IEndlessXpReward
|
IEndlessXpReward,
|
||||||
|
IPersonalGoalProgressDatabase,
|
||||||
|
IPersonalGoalProgressClient,
|
||||||
|
IKubrowPetPrintClient,
|
||||||
|
IKubrowPetPrintDatabase
|
||||||
} from "../../types/inventoryTypes/inventoryTypes";
|
} from "../../types/inventoryTypes/inventoryTypes";
|
||||||
import { IOid } from "../../types/commonTypes";
|
import { IOid } from "../../types/commonTypes";
|
||||||
import {
|
import {
|
||||||
@ -251,12 +253,6 @@ const ArchonCrystalUpgradeSchema = new Schema<IArchonCrystalUpgrade>(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
ArchonCrystalUpgradeSchema.set("toJSON", {
|
|
||||||
transform(_document, returnedObject) {
|
|
||||||
delete returnedObject.__v;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const boosterSchema = new Schema<IBooster>(
|
const boosterSchema = new Schema<IBooster>(
|
||||||
{
|
{
|
||||||
ExpiryDate: Number,
|
ExpiryDate: Number,
|
||||||
@ -377,7 +373,7 @@ FlavourItemSchema.set("toJSON", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const MailboxSchema = new Schema<IMailboxDatabase>(
|
/*const MailboxSchema = new Schema<IMailboxDatabase>(
|
||||||
{
|
{
|
||||||
LastInboxId: Schema.Types.ObjectId
|
LastInboxId: Schema.Types.ObjectId
|
||||||
},
|
},
|
||||||
@ -390,7 +386,7 @@ MailboxSchema.set("toJSON", {
|
|||||||
delete mailboxDatabase.__v;
|
delete mailboxDatabase.__v;
|
||||||
(returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId);
|
(returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId);
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
|
|
||||||
const DuviriInfoSchema = new Schema<IDuviriInfo>(
|
const DuviriInfoSchema = new Schema<IDuviriInfo>(
|
||||||
{
|
{
|
||||||
@ -463,11 +459,35 @@ const discoveredMarkerSchema = new Schema<IDiscoveredMarker>(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const personalGoalProgressSchema = new Schema<IPersonalGoalProgressDatabase>(
|
||||||
|
{
|
||||||
|
Best: Number,
|
||||||
|
Count: Number,
|
||||||
|
Tag: String,
|
||||||
|
goalId: Types.ObjectId
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
personalGoalProgressSchema.set("toJSON", {
|
||||||
|
virtuals: true,
|
||||||
|
transform(_doc, obj) {
|
||||||
|
const db = obj as IPersonalGoalProgressDatabase;
|
||||||
|
const client = obj as IPersonalGoalProgressClient;
|
||||||
|
|
||||||
|
client._id = toOid(db.goalId);
|
||||||
|
|
||||||
|
delete obj.goalId;
|
||||||
|
delete obj.__v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const challengeProgressSchema = new Schema<IChallengeProgress>(
|
const challengeProgressSchema = new Schema<IChallengeProgress>(
|
||||||
{
|
{
|
||||||
Progress: Number,
|
Progress: Number,
|
||||||
Name: String,
|
Completed: { type: [String], default: undefined },
|
||||||
Completed: [String]
|
ReceivedJunctionReward: Boolean,
|
||||||
|
Name: { type: String, required: true }
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
@ -990,6 +1010,27 @@ const traitsSchema = new Schema<ITraits>(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const kubrowPetPrintSchema = new Schema<IKubrowPetPrintDatabase>({
|
||||||
|
ItemType: String,
|
||||||
|
Name: String,
|
||||||
|
IsMale: Boolean,
|
||||||
|
Size: Number,
|
||||||
|
DominantTraits: traitsSchema,
|
||||||
|
RecessiveTraits: traitsSchema
|
||||||
|
});
|
||||||
|
kubrowPetPrintSchema.set("toJSON", {
|
||||||
|
virtuals: true,
|
||||||
|
transform(_doc, obj) {
|
||||||
|
const db = obj as IKubrowPetPrintDatabase;
|
||||||
|
const client = obj as IKubrowPetPrintClient;
|
||||||
|
|
||||||
|
client.ItemId = toOid(db._id);
|
||||||
|
|
||||||
|
delete obj._id;
|
||||||
|
delete obj.__v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const detailsSchema = new Schema<IKubrowPetDetailsDatabase>(
|
const detailsSchema = new Schema<IKubrowPetDetailsDatabase>(
|
||||||
{
|
{
|
||||||
Name: String,
|
Name: String,
|
||||||
@ -1079,6 +1120,11 @@ EquipmentSchema.set("toJSON", {
|
|||||||
if (db.UmbraDate) {
|
if (db.UmbraDate) {
|
||||||
client.UmbraDate = toMongoDate(db.UmbraDate);
|
client.UmbraDate = toMongoDate(db.UmbraDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client.ArchonCrystalUpgrades) {
|
||||||
|
// For some reason, mongoose turns empty objects here into nulls, so we have to fix it.
|
||||||
|
client.ArchonCrystalUpgrades = client.ArchonCrystalUpgrades.map(x => (x as unknown) ?? {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1371,10 +1417,10 @@ lastSortieRewardSchema.set("toJSON", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortieRewardAttenutationSchema = new Schema<ISortieRewardAttenuation>(
|
const rewardAttenutationSchema = new Schema<IRewardAttenuation>(
|
||||||
{
|
{
|
||||||
Tag: String,
|
Tag: { type: String, required: true },
|
||||||
Atten: Number
|
Atten: { type: Number, required: true }
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
@ -1488,7 +1534,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
|
|
||||||
KubrowPetEggs: [kubrowPetEggSchema],
|
KubrowPetEggs: [kubrowPetEggSchema],
|
||||||
//Prints Cat(3 Prints)\Kubrow(2 Prints) Pets
|
//Prints Cat(3 Prints)\Kubrow(2 Prints) Pets
|
||||||
//KubrowPetPrints: [Schema.Types.Mixed],
|
KubrowPetPrints: [kubrowPetPrintSchema],
|
||||||
|
|
||||||
//Item for EquippedGear example:Scaner,LoadoutTechSummon etc
|
//Item for EquippedGear example:Scaner,LoadoutTechSummon etc
|
||||||
Consumables: [typeCountSchema],
|
Consumables: [typeCountSchema],
|
||||||
@ -1602,6 +1648,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
PendingSpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined },
|
PendingSpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined },
|
||||||
SpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined },
|
SpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined },
|
||||||
|
|
||||||
|
//Darvo Deal
|
||||||
|
UsedDailyDeals: [String],
|
||||||
|
|
||||||
//New Quest Email
|
//New Quest Email
|
||||||
EmailItems: [typeCountSchema],
|
EmailItems: [typeCountSchema],
|
||||||
|
|
||||||
@ -1617,7 +1666,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
CompletedSorties: [String],
|
CompletedSorties: [String],
|
||||||
LastSortieReward: { type: [lastSortieRewardSchema], default: undefined },
|
LastSortieReward: { type: [lastSortieRewardSchema], default: undefined },
|
||||||
LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined },
|
LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined },
|
||||||
SortieRewardAttenuation: { type: [sortieRewardAttenutationSchema], default: undefined },
|
SortieRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined },
|
||||||
|
|
||||||
// Resource Extractor Drones
|
// Resource Extractor Drones
|
||||||
Drones: [droneSchema],
|
Drones: [droneSchema],
|
||||||
@ -1631,7 +1680,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
//CompletedJobs: [Schema.Types.Mixed],
|
//CompletedJobs: [Schema.Types.Mixed],
|
||||||
|
|
||||||
//Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258,
|
//Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258,
|
||||||
//PersonalGoalProgress: [Schema.Types.Mixed],
|
PersonalGoalProgress: { type: [personalGoalProgressSchema], default: undefined },
|
||||||
|
|
||||||
//Setting interface Style
|
//Setting interface Style
|
||||||
ThemeStyle: String,
|
ThemeStyle: String,
|
||||||
@ -1702,9 +1751,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
//Unknown and system
|
//Unknown and system
|
||||||
DuviriInfo: DuviriInfoSchema,
|
DuviriInfo: DuviriInfoSchema,
|
||||||
LastInventorySync: Schema.Types.ObjectId,
|
LastInventorySync: Schema.Types.ObjectId,
|
||||||
Mailbox: MailboxSchema,
|
//Mailbox: MailboxSchema,
|
||||||
HandlerPoints: Number,
|
HandlerPoints: Number,
|
||||||
ChallengesFixVersion: { type: Number, default: 6 },
|
ChallengesFixVersion: Number,
|
||||||
PlayedParkourTutorial: Boolean,
|
PlayedParkourTutorial: Boolean,
|
||||||
//ActiveLandscapeTraps: [Schema.Types.Mixed],
|
//ActiveLandscapeTraps: [Schema.Types.Mixed],
|
||||||
//RepVotes: [Schema.Types.Mixed],
|
//RepVotes: [Schema.Types.Mixed],
|
||||||
@ -1718,7 +1767,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
//ChallengeInstanceStates: [Schema.Types.Mixed],
|
//ChallengeInstanceStates: [Schema.Types.Mixed],
|
||||||
RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined },
|
RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined },
|
||||||
//Robotics: [Schema.Types.Mixed],
|
//Robotics: [Schema.Types.Mixed],
|
||||||
//UsedDailyDeals: [Schema.Types.Mixed],
|
|
||||||
CollectibleSeries: { type: [collectibleEntrySchema], default: undefined },
|
CollectibleSeries: { type: [collectibleEntrySchema], default: undefined },
|
||||||
HasResetAccount: { type: Boolean, default: false },
|
HasResetAccount: { type: Boolean, default: false },
|
||||||
|
|
||||||
@ -1755,7 +1803,11 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined },
|
BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined },
|
||||||
LockedWeaponGroup: { type: lockedWeaponGroupSchema, default: undefined },
|
LockedWeaponGroup: { type: lockedWeaponGroupSchema, default: undefined },
|
||||||
|
|
||||||
HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined }
|
HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined },
|
||||||
|
|
||||||
|
ClaimedJunctionChallengeRewards: { type: [String], default: undefined },
|
||||||
|
|
||||||
|
SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined }
|
||||||
},
|
},
|
||||||
{ timestamps: { createdAt: "Created", updatedAt: false } }
|
{ timestamps: { createdAt: "Created", updatedAt: false } }
|
||||||
);
|
);
|
||||||
@ -1825,6 +1877,7 @@ export type InventoryDocumentProps = {
|
|||||||
CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
|
CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
|
||||||
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
|
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
|
||||||
CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
|
CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
|
||||||
|
KubrowPetPrints: Types.DocumentArray<IKubrowPetPrintDatabase>;
|
||||||
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
|
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
|
@ -11,13 +11,13 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
|
|||||||
email: { type: String, required: true, unique: true },
|
email: { type: String, required: true, unique: true },
|
||||||
password: { type: String, required: true },
|
password: { type: String, required: true },
|
||||||
DisplayName: { type: String, required: true, unique: true },
|
DisplayName: { type: String, required: true, unique: true },
|
||||||
CountryCode: { type: String, required: true },
|
CountryCode: { type: String, default: "" },
|
||||||
ClientType: { type: String },
|
ClientType: { type: String },
|
||||||
CrossPlatformAllowed: { type: Boolean, required: true },
|
CrossPlatformAllowed: { type: Boolean, default: true },
|
||||||
ForceLogoutVersion: { type: Number, required: true },
|
ForceLogoutVersion: { type: Number, default: 0 },
|
||||||
AmazonAuthToken: { type: String },
|
AmazonAuthToken: { type: String },
|
||||||
AmazonRefreshToken: { type: String },
|
AmazonRefreshToken: { type: String },
|
||||||
ConsentNeeded: { type: Boolean, required: true },
|
ConsentNeeded: { type: Boolean, default: false },
|
||||||
TrackedSettings: { type: [String], default: [] },
|
TrackedSettings: { type: [String], default: [] },
|
||||||
Nonce: { type: Number, default: 0 },
|
Nonce: { type: Number, default: 0 },
|
||||||
BuildLabel: String,
|
BuildLabel: String,
|
||||||
@ -25,7 +25,8 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
|
|||||||
LastLogin: { type: Date, default: 0 },
|
LastLogin: { type: Date, default: 0 },
|
||||||
LatestEventMessageDate: { type: Date, default: 0 },
|
LatestEventMessageDate: { type: Date, default: 0 },
|
||||||
LastLoginRewardDate: { type: Number, default: 0 },
|
LastLoginRewardDate: { type: Number, default: 0 },
|
||||||
LoginDays: { type: Number, default: 1 }
|
LoginDays: { type: Number, default: 1 },
|
||||||
|
DailyFirstWinDate: { type: Number, default: 0 }
|
||||||
},
|
},
|
||||||
opts
|
opts
|
||||||
);
|
);
|
||||||
|
@ -40,6 +40,7 @@ const placedDecosSchema = new Schema<IPlacedDecosDatabase>(
|
|||||||
Pos: [Number],
|
Pos: [Number],
|
||||||
Rot: [Number],
|
Rot: [Number],
|
||||||
Scale: Number,
|
Scale: Number,
|
||||||
|
Sockets: Number,
|
||||||
PictureFrameInfo: { type: pictureFrameInfoSchema, default: undefined }
|
PictureFrameInfo: { type: pictureFrameInfoSchema, default: undefined }
|
||||||
},
|
},
|
||||||
{ id: false }
|
{ id: false }
|
||||||
|
30
src/models/worldStateModel.ts
Normal file
30
src/models/worldStateModel.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { IDailyDealDatabase, IFissureDatabase } from "@/src/types/worldStateTypes";
|
||||||
|
import { model, Schema } from "mongoose";
|
||||||
|
|
||||||
|
const fissureSchema = new Schema<IFissureDatabase>({
|
||||||
|
Activation: Date,
|
||||||
|
Expiry: Date,
|
||||||
|
Node: String, // must be unique
|
||||||
|
Modifier: String,
|
||||||
|
Hard: Boolean
|
||||||
|
});
|
||||||
|
|
||||||
|
fissureSchema.index({ Expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries.
|
||||||
|
|
||||||
|
export const Fissure = model<IFissureDatabase>("Fissure", fissureSchema);
|
||||||
|
|
||||||
|
const dailyDealSchema = new Schema<IDailyDealDatabase>({
|
||||||
|
StoreItem: { type: String, required: true },
|
||||||
|
Activation: { type: Date, required: true },
|
||||||
|
Expiry: { type: Date, required: true },
|
||||||
|
Discount: { type: Number, required: true },
|
||||||
|
OriginalPrice: { type: Number, required: true },
|
||||||
|
SalePrice: { type: Number, required: true },
|
||||||
|
AmountTotal: { type: Number, required: true },
|
||||||
|
AmountSold: { type: Number, required: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
dailyDealSchema.index({ StoreItem: 1 }, { unique: true });
|
||||||
|
dailyDealSchema.index({ Expiry: 1 }, { expireAfterSeconds: 86400 });
|
||||||
|
|
||||||
|
export const DailyDeal = model<IDailyDealDatabase>("DailyDeal", dailyDealSchema);
|
@ -19,6 +19,7 @@ import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootCo
|
|||||||
import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController";
|
import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController";
|
||||||
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
|
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
|
||||||
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
|
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
|
||||||
|
import { claimJunctionChallengeRewardController } from "@/src/controllers/api/claimJunctionChallengeRewardController";
|
||||||
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
|
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
|
||||||
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
|
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
|
||||||
import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController";
|
import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController";
|
||||||
@ -33,6 +34,7 @@ import { createAllianceController } from "@/src/controllers/api/createAllianceCo
|
|||||||
import { createGuildController } from "@/src/controllers/api/createGuildController";
|
import { createGuildController } from "@/src/controllers/api/createGuildController";
|
||||||
import { creditsController } from "@/src/controllers/api/creditsController";
|
import { creditsController } from "@/src/controllers/api/creditsController";
|
||||||
import { crewMembersController } from "@/src/controllers/api/crewMembersController";
|
import { crewMembersController } from "@/src/controllers/api/crewMembersController";
|
||||||
|
import { crewShipFusionController } from "@/src/controllers/api/crewShipFusionController";
|
||||||
import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
|
import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
|
||||||
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
|
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
|
||||||
import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
|
import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
|
||||||
@ -132,6 +134,7 @@ import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDeco
|
|||||||
import { setShipCustomizationsController } from "@/src/controllers/api/setShipCustomizationsController";
|
import { setShipCustomizationsController } from "@/src/controllers/api/setShipCustomizationsController";
|
||||||
import { setShipFavouriteLoadoutController } from "@/src/controllers/api/setShipFavouriteLoadoutController";
|
import { setShipFavouriteLoadoutController } from "@/src/controllers/api/setShipFavouriteLoadoutController";
|
||||||
import { setShipVignetteController } from "@/src/controllers/api/setShipVignetteController";
|
import { setShipVignetteController } from "@/src/controllers/api/setShipVignetteController";
|
||||||
|
import { setSuitInfectionController } from "@/src/controllers/api/setSuitInfectionController";
|
||||||
import { setSupportedSyndicateController } from "@/src/controllers/api/setSupportedSyndicateController";
|
import { setSupportedSyndicateController } from "@/src/controllers/api/setSupportedSyndicateController";
|
||||||
import { setWeaponSkillTreeController } from "@/src/controllers/api/setWeaponSkillTreeController";
|
import { setWeaponSkillTreeController } from "@/src/controllers/api/setWeaponSkillTreeController";
|
||||||
import { shipDecorationsController } from "@/src/controllers/api/shipDecorationsController";
|
import { shipDecorationsController } from "@/src/controllers/api/shipDecorationsController";
|
||||||
@ -147,6 +150,7 @@ import { syndicateStandingBonusController } from "@/src/controllers/api/syndicat
|
|||||||
import { tauntHistoryController } from "@/src/controllers/api/tauntHistoryController";
|
import { tauntHistoryController } from "@/src/controllers/api/tauntHistoryController";
|
||||||
import { tradingController } from "@/src/controllers/api/tradingController";
|
import { tradingController } from "@/src/controllers/api/tradingController";
|
||||||
import { trainingResultController } from "@/src/controllers/api/trainingResultController";
|
import { trainingResultController } from "@/src/controllers/api/trainingResultController";
|
||||||
|
import { umbraController } from "@/src/controllers/api/umbraController";
|
||||||
import { unlockShipFeatureController } from "@/src/controllers/api/unlockShipFeatureController";
|
import { unlockShipFeatureController } from "@/src/controllers/api/unlockShipFeatureController";
|
||||||
import { updateAlignmentController } from "@/src/controllers/api/updateAlignmentController";
|
import { updateAlignmentController } from "@/src/controllers/api/updateAlignmentController";
|
||||||
import { updateChallengeProgressController } from "@/src/controllers/api/updateChallengeProgressController";
|
import { updateChallengeProgressController } from "@/src/controllers/api/updateChallengeProgressController";
|
||||||
@ -234,6 +238,7 @@ apiRouter.post("/artifacts.php", artifactsController);
|
|||||||
apiRouter.post("/artifactTransmutation.php", artifactTransmutationController);
|
apiRouter.post("/artifactTransmutation.php", artifactTransmutationController);
|
||||||
apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
|
apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
|
||||||
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
|
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
|
||||||
|
apiRouter.post("/claimJunctionChallengeReward.php", claimJunctionChallengeRewardController);
|
||||||
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
|
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
|
||||||
apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
|
apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
|
||||||
apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?)
|
apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?)
|
||||||
@ -245,6 +250,7 @@ apiRouter.post("/contributeToVault.php", contributeToVaultController);
|
|||||||
apiRouter.post("/createAlliance.php", createAllianceController);
|
apiRouter.post("/createAlliance.php", createAllianceController);
|
||||||
apiRouter.post("/createGuild.php", createGuildController);
|
apiRouter.post("/createGuild.php", createGuildController);
|
||||||
apiRouter.post("/crewMembers.php", crewMembersController);
|
apiRouter.post("/crewMembers.php", crewMembersController);
|
||||||
|
apiRouter.post("/crewShipFusion.php", crewShipFusionController);
|
||||||
apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
|
apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
|
||||||
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
|
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
|
||||||
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
|
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
|
||||||
@ -280,6 +286,7 @@ apiRouter.post("/inventorySlots.php", inventorySlotsController);
|
|||||||
apiRouter.post("/joinSession.php", joinSessionController);
|
apiRouter.post("/joinSession.php", joinSessionController);
|
||||||
apiRouter.post("/login.php", loginController);
|
apiRouter.post("/login.php", loginController);
|
||||||
apiRouter.post("/loginRewardsSelection.php", loginRewardsSelectionController);
|
apiRouter.post("/loginRewardsSelection.php", loginRewardsSelectionController);
|
||||||
|
apiRouter.post("/logout.php", logoutController); // from ~U16, don't know when they changed it to GET
|
||||||
apiRouter.post("/maturePet.php", maturePetController);
|
apiRouter.post("/maturePet.php", maturePetController);
|
||||||
apiRouter.post("/missionInventoryUpdate.php", missionInventoryUpdateController);
|
apiRouter.post("/missionInventoryUpdate.php", missionInventoryUpdateController);
|
||||||
apiRouter.post("/modularWeaponCrafting.php", modularWeaponCraftingController);
|
apiRouter.post("/modularWeaponCrafting.php", modularWeaponCraftingController);
|
||||||
@ -317,6 +324,7 @@ apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController);
|
|||||||
apiRouter.post("/setShipCustomizations.php", setShipCustomizationsController);
|
apiRouter.post("/setShipCustomizations.php", setShipCustomizationsController);
|
||||||
apiRouter.post("/setShipFavouriteLoadout.php", setShipFavouriteLoadoutController);
|
apiRouter.post("/setShipFavouriteLoadout.php", setShipFavouriteLoadoutController);
|
||||||
apiRouter.post("/setShipVignette.php", setShipVignetteController);
|
apiRouter.post("/setShipVignette.php", setShipVignetteController);
|
||||||
|
apiRouter.post("/setSuitInfection.php", setSuitInfectionController);
|
||||||
apiRouter.post("/setWeaponSkillTree.php", setWeaponSkillTreeController);
|
apiRouter.post("/setWeaponSkillTree.php", setWeaponSkillTreeController);
|
||||||
apiRouter.post("/shipDecorations.php", shipDecorationsController);
|
apiRouter.post("/shipDecorations.php", shipDecorationsController);
|
||||||
apiRouter.post("/startCollectibleEntry.php", startCollectibleEntryController);
|
apiRouter.post("/startCollectibleEntry.php", startCollectibleEntryController);
|
||||||
@ -327,6 +335,7 @@ apiRouter.post("/syndicateSacrifice.php", syndicateSacrificeController);
|
|||||||
apiRouter.post("/syndicateStandingBonus.php", syndicateStandingBonusController);
|
apiRouter.post("/syndicateStandingBonus.php", syndicateStandingBonusController);
|
||||||
apiRouter.post("/tauntHistory.php", tauntHistoryController);
|
apiRouter.post("/tauntHistory.php", tauntHistoryController);
|
||||||
apiRouter.post("/trainingResult.php", trainingResultController);
|
apiRouter.post("/trainingResult.php", trainingResultController);
|
||||||
|
apiRouter.post("/umbra.php", umbraController);
|
||||||
apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController);
|
apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController);
|
||||||
apiRouter.post("/updateAlignment.php", updateAlignmentController);
|
apiRouter.post("/updateAlignment.php", updateAlignmentController);
|
||||||
apiRouter.post("/updateChallengeProgress.php", updateChallengeProgressController);
|
apiRouter.post("/updateChallengeProgress.php", updateChallengeProgressController);
|
||||||
|
@ -11,6 +11,9 @@ import { renameAccountController } from "@/src/controllers/custom/renameAccountC
|
|||||||
import { ircDroppedController } from "@/src/controllers/custom/ircDroppedController";
|
import { ircDroppedController } from "@/src/controllers/custom/ircDroppedController";
|
||||||
import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController";
|
import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController";
|
||||||
import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController";
|
import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController";
|
||||||
|
import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController";
|
||||||
|
import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController";
|
||||||
|
import { addMissingHelminthBlueprintsController } from "@/src/controllers/custom/addMissingHelminthBlueprintsController";
|
||||||
|
|
||||||
import { createAccountController } from "@/src/controllers/custom/createAccountController";
|
import { createAccountController } from "@/src/controllers/custom/createAccountController";
|
||||||
import { createMessageController } from "@/src/controllers/custom/createMessageController";
|
import { createMessageController } from "@/src/controllers/custom/createMessageController";
|
||||||
@ -20,10 +23,10 @@ import { addXpController } from "@/src/controllers/custom/addXpController";
|
|||||||
import { importController } from "@/src/controllers/custom/importController";
|
import { importController } from "@/src/controllers/custom/importController";
|
||||||
import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController";
|
import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController";
|
||||||
import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
|
import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
|
||||||
|
import { setBoosterController } from "@/src/controllers/custom/setBoosterController";
|
||||||
|
import { updateFingerprintController } from "@/src/controllers/custom/updateFingerprintController";
|
||||||
|
|
||||||
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
|
import { getConfigController, setConfigController } from "@/src/controllers/custom/configController";
|
||||||
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
|
|
||||||
import { setBoosterController } from "../controllers/custom/setBoosterController";
|
|
||||||
|
|
||||||
const customRouter = express.Router();
|
const customRouter = express.Router();
|
||||||
|
|
||||||
@ -38,6 +41,9 @@ customRouter.get("/renameAccount", renameAccountController);
|
|||||||
customRouter.get("/ircDropped", ircDroppedController);
|
customRouter.get("/ircDropped", ircDroppedController);
|
||||||
customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
|
customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
|
||||||
customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
|
customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
|
||||||
|
customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
|
||||||
|
customRouter.get("/completeAllMissions", completeAllMissionsController);
|
||||||
|
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
|
||||||
|
|
||||||
customRouter.post("/createAccount", createAccountController);
|
customRouter.post("/createAccount", createAccountController);
|
||||||
customRouter.post("/createMessage", createMessageController);
|
customRouter.post("/createMessage", createMessageController);
|
||||||
@ -48,8 +54,9 @@ customRouter.post("/import", importController);
|
|||||||
customRouter.post("/manageQuests", manageQuestsController);
|
customRouter.post("/manageQuests", manageQuestsController);
|
||||||
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
|
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
|
||||||
customRouter.post("/setBooster", setBoosterController);
|
customRouter.post("/setBooster", setBoosterController);
|
||||||
|
customRouter.post("/updateFingerprint", updateFingerprintController);
|
||||||
|
|
||||||
customRouter.get("/config", getConfigDataController);
|
customRouter.post("/getConfig", getConfigController);
|
||||||
customRouter.post("/config", updateConfigDataController);
|
customRouter.post("/setConfig", setConfigController);
|
||||||
|
|
||||||
export { customRouter };
|
export { customRouter };
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { repoDir, rootDir } from "@/src/helpers/pathHelper";
|
import { repoDir, rootDir } from "@/src/helpers/pathHelper";
|
||||||
|
import { args } from "@/src/helpers/commandLineArguments";
|
||||||
|
|
||||||
|
const baseDir = args.dev ? repoDir : rootDir;
|
||||||
|
|
||||||
const webuiRouter = express.Router();
|
const webuiRouter = express.Router();
|
||||||
|
|
||||||
@ -19,29 +22,29 @@ webuiRouter.use("/webui", (req, res, next) => {
|
|||||||
|
|
||||||
// Serve virtual routes
|
// Serve virtual routes
|
||||||
webuiRouter.get("/webui/inventory", (_req, res) => {
|
webuiRouter.get("/webui/inventory", (_req, res) => {
|
||||||
res.sendFile(path.join(rootDir, "static/webui/index.html"));
|
res.sendFile(path.join(baseDir, "static/webui/index.html"));
|
||||||
});
|
});
|
||||||
webuiRouter.get(/webui\/powersuit\/(.+)/, (_req, res) => {
|
webuiRouter.get("/webui/detailedView", (_req, res) => {
|
||||||
res.sendFile(path.join(rootDir, "static/webui/index.html"));
|
res.sendFile(path.join(baseDir, "static/webui/index.html"));
|
||||||
});
|
});
|
||||||
webuiRouter.get("/webui/mods", (_req, res) => {
|
webuiRouter.get("/webui/mods", (_req, res) => {
|
||||||
res.sendFile(path.join(rootDir, "static/webui/index.html"));
|
res.sendFile(path.join(baseDir, "static/webui/index.html"));
|
||||||
});
|
});
|
||||||
webuiRouter.get("/webui/settings", (_req, res) => {
|
webuiRouter.get("/webui/settings", (_req, res) => {
|
||||||
res.sendFile(path.join(rootDir, "static/webui/index.html"));
|
res.sendFile(path.join(baseDir, "static/webui/index.html"));
|
||||||
});
|
});
|
||||||
webuiRouter.get("/webui/quests", (_req, res) => {
|
webuiRouter.get("/webui/quests", (_req, res) => {
|
||||||
res.sendFile(path.join(rootDir, "static/webui/index.html"));
|
res.sendFile(path.join(baseDir, "static/webui/index.html"));
|
||||||
});
|
});
|
||||||
webuiRouter.get("/webui/cheats", (_req, res) => {
|
webuiRouter.get("/webui/cheats", (_req, res) => {
|
||||||
res.sendFile(path.join(rootDir, "static/webui/index.html"));
|
res.sendFile(path.join(baseDir, "static/webui/index.html"));
|
||||||
});
|
});
|
||||||
webuiRouter.get("/webui/import", (_req, res) => {
|
webuiRouter.get("/webui/import", (_req, res) => {
|
||||||
res.sendFile(path.join(rootDir, "static/webui/index.html"));
|
res.sendFile(path.join(baseDir, "static/webui/index.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve static files
|
// Serve static files
|
||||||
webuiRouter.use("/webui", express.static(path.join(rootDir, "static/webui")));
|
webuiRouter.use("/webui", express.static(path.join(baseDir, "static/webui")));
|
||||||
|
|
||||||
// Serve favicon
|
// Serve favicon
|
||||||
webuiRouter.get("/favicon.ico", (_req, res) => {
|
webuiRouter.get("/favicon.ico", (_req, res) => {
|
||||||
@ -58,7 +61,7 @@ webuiRouter.get("/webui/riven-tool/RivenParser.js", (_req, res) => {
|
|||||||
|
|
||||||
// Serve translations
|
// Serve translations
|
||||||
webuiRouter.get("/translations/:file", (req, res) => {
|
webuiRouter.get("/translations/:file", (req, res) => {
|
||||||
res.sendFile(path.join(rootDir, `static/webui/translations/${req.params.file}`));
|
res.sendFile(path.join(baseDir, `static/webui/translations/${req.params.file}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
export { webuiRouter };
|
export { webuiRouter };
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { repoDir } from "@/src/helpers/pathHelper";
|
import { repoDir } from "@/src/helpers/pathHelper";
|
||||||
|
import { args } from "@/src/helpers/commandLineArguments";
|
||||||
|
|
||||||
interface IConfig {
|
export interface IConfig {
|
||||||
mongodbUrl: string;
|
mongodbUrl: string;
|
||||||
logger: {
|
logger: {
|
||||||
files: boolean;
|
files: boolean;
|
||||||
@ -18,13 +19,16 @@ interface IConfig {
|
|||||||
skipTutorial?: boolean;
|
skipTutorial?: boolean;
|
||||||
skipAllDialogue?: boolean;
|
skipAllDialogue?: boolean;
|
||||||
unlockAllScans?: boolean;
|
unlockAllScans?: boolean;
|
||||||
unlockAllMissions?: boolean;
|
|
||||||
infiniteCredits?: boolean;
|
infiniteCredits?: boolean;
|
||||||
infinitePlatinum?: boolean;
|
infinitePlatinum?: boolean;
|
||||||
infiniteEndo?: boolean;
|
infiniteEndo?: boolean;
|
||||||
infiniteRegalAya?: boolean;
|
infiniteRegalAya?: boolean;
|
||||||
infiniteHelminthMaterials?: boolean;
|
infiniteHelminthMaterials?: boolean;
|
||||||
claimingBlueprintRefundsIngredients?: boolean;
|
claimingBlueprintRefundsIngredients?: boolean;
|
||||||
|
dontSubtractPurchaseCreditCost?: boolean;
|
||||||
|
dontSubtractPurchasePlatinumCost?: boolean;
|
||||||
|
dontSubtractPurchaseItemCost?: boolean;
|
||||||
|
dontSubtractPurchaseStandingCost?: boolean;
|
||||||
dontSubtractVoidTraces?: boolean;
|
dontSubtractVoidTraces?: boolean;
|
||||||
dontSubtractConsumables?: boolean;
|
dontSubtractConsumables?: boolean;
|
||||||
unlockAllShipFeatures?: boolean;
|
unlockAllShipFeatures?: boolean;
|
||||||
@ -44,7 +48,11 @@ interface IConfig {
|
|||||||
noVendorPurchaseLimits?: boolean;
|
noVendorPurchaseLimits?: boolean;
|
||||||
noDeathMarks?: boolean;
|
noDeathMarks?: boolean;
|
||||||
noKimCooldowns?: boolean;
|
noKimCooldowns?: boolean;
|
||||||
|
fullyStockedVendors?: boolean;
|
||||||
|
baroAlwaysAvailable?: boolean;
|
||||||
|
baroFullyStocked?: boolean;
|
||||||
syndicateMissionsRepeatable?: boolean;
|
syndicateMissionsRepeatable?: boolean;
|
||||||
|
unlockAllProfitTakerStages?: boolean;
|
||||||
instantFinishRivenChallenge?: boolean;
|
instantFinishRivenChallenge?: boolean;
|
||||||
instantResourceExtractorDrones?: boolean;
|
instantResourceExtractorDrones?: boolean;
|
||||||
noResourceExtractorDronesDamage?: boolean;
|
noResourceExtractorDronesDamage?: boolean;
|
||||||
@ -55,20 +63,36 @@ interface IConfig {
|
|||||||
noDojoResearchCosts?: boolean;
|
noDojoResearchCosts?: boolean;
|
||||||
noDojoResearchTime?: boolean;
|
noDojoResearchTime?: boolean;
|
||||||
fastClanAscension?: boolean;
|
fastClanAscension?: boolean;
|
||||||
|
missionsCanGiveAllRelics?: boolean;
|
||||||
|
unlockAllSimarisResearchEntries?: boolean;
|
||||||
|
disableDailyTribute?: boolean;
|
||||||
spoofMasteryRank?: number;
|
spoofMasteryRank?: number;
|
||||||
|
relicRewardItemCountMultiplier?: number;
|
||||||
|
nightwaveStandingMultiplier?: number;
|
||||||
|
unfaithfulBugFixes?: {
|
||||||
|
ignore1999LastRegionPlayed?: boolean;
|
||||||
|
fixXtraCheeseTimer?: boolean;
|
||||||
|
};
|
||||||
worldState?: {
|
worldState?: {
|
||||||
creditBoost?: boolean;
|
creditBoost?: boolean;
|
||||||
affinityBoost?: boolean;
|
affinityBoost?: boolean;
|
||||||
resourceBoost?: boolean;
|
resourceBoost?: boolean;
|
||||||
starDays?: boolean;
|
starDays?: boolean;
|
||||||
|
galleonOfGhouls?: number;
|
||||||
eidolonOverride?: string;
|
eidolonOverride?: string;
|
||||||
vallisOverride?: string;
|
vallisOverride?: string;
|
||||||
|
duviriOverride?: string;
|
||||||
nightwaveOverride?: string;
|
nightwaveOverride?: string;
|
||||||
|
allTheFissures?: string;
|
||||||
|
circuitGameModes?: string[];
|
||||||
|
darvoStockMultiplier?: number;
|
||||||
|
};
|
||||||
|
dev?: {
|
||||||
|
keepVendorsExpired?: boolean;
|
||||||
};
|
};
|
||||||
nightwaveStandingMultiplier?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const configPath = path.join(repoDir, "config.json");
|
export const configPath = path.join(repoDir, args.configPath ?? "config.json");
|
||||||
|
|
||||||
export const config: IConfig = {
|
export const config: IConfig = {
|
||||||
mongodbUrl: "mongodb://127.0.0.1:27017/openWF",
|
mongodbUrl: "mongodb://127.0.0.1:27017/openWF",
|
||||||
@ -80,11 +104,13 @@ export const config: IConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const loadConfig = (): void => {
|
export const loadConfig = (): void => {
|
||||||
|
const newConfig = JSON.parse(fs.readFileSync(configPath, "utf-8")) as IConfig;
|
||||||
|
|
||||||
// Set all values to undefined now so if the new config.json omits some fields that were previously present, it's correct in-memory.
|
// Set all values to undefined now so if the new config.json omits some fields that were previously present, it's correct in-memory.
|
||||||
for (const key of Object.keys(config)) {
|
for (const key of Object.keys(config)) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
||||||
(config as any)[key] = undefined;
|
(config as any)[key] = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(config, JSON.parse(fs.readFileSync(configPath, "utf-8")));
|
Object.assign(config, newConfig);
|
||||||
};
|
};
|
||||||
|
@ -1,27 +1,35 @@
|
|||||||
import fs from "fs";
|
import chokidar from "chokidar";
|
||||||
import fsPromises from "fs/promises";
|
import fsPromises from "fs/promises";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { config, configPath, loadConfig } from "./configService";
|
import { config, configPath, loadConfig } from "./configService";
|
||||||
import { getWebPorts, startWebServer, stopWebServer } from "./webService";
|
import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService";
|
||||||
|
import { Inbox } from "../models/inboxModel";
|
||||||
|
|
||||||
let amnesia = false;
|
let amnesia = false;
|
||||||
fs.watchFile(configPath, () => {
|
chokidar.watch(configPath).on("change", () => {
|
||||||
if (amnesia) {
|
if (amnesia) {
|
||||||
amnesia = false;
|
amnesia = false;
|
||||||
} else {
|
} else {
|
||||||
logger.info("Detected a change to config.json, reloading its contents.");
|
logger.info("Detected a change to config file, reloading its contents.");
|
||||||
try {
|
try {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Failed to reload config.json. Did you delete it?! Execution cannot continue.");
|
logger.error("Config changes were not applied: " + (e as Error).message);
|
||||||
process.exit(1);
|
return;
|
||||||
}
|
}
|
||||||
validateConfig();
|
validateConfig();
|
||||||
|
syncConfigWithDatabase();
|
||||||
|
|
||||||
const webPorts = getWebPorts();
|
const webPorts = getWebPorts();
|
||||||
if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) {
|
if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) {
|
||||||
logger.info(`Restarting web server to apply port changes.`);
|
logger.info(`Restarting web server to apply port changes.`);
|
||||||
|
|
||||||
|
// Tell webui clients to reload with new port
|
||||||
|
sendWsBroadcast({ ports: { http: config.httpPort, https: config.httpsPort } });
|
||||||
|
|
||||||
void stopWebServer().then(startWebServer);
|
void stopWebServer().then(startWebServer);
|
||||||
|
} else {
|
||||||
|
sendWsBroadcast({ config_reloaded: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -40,19 +48,29 @@ export const validateConfig = (): void => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
config.worldState?.galleonOfGhouls &&
|
||||||
|
config.worldState.galleonOfGhouls != 1 &&
|
||||||
|
config.worldState.galleonOfGhouls != 2 &&
|
||||||
|
config.worldState.galleonOfGhouls != 3
|
||||||
|
) {
|
||||||
|
config.worldState.galleonOfGhouls = 0;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
if (modified) {
|
if (modified) {
|
||||||
logger.info(`Updating config.json to fix some issues with it.`);
|
logger.info(`Updating config file to fix some issues with it.`);
|
||||||
void saveConfig();
|
void saveConfig();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateConfig = async (data: string): Promise<void> => {
|
|
||||||
amnesia = true;
|
|
||||||
await fsPromises.writeFile(configPath, data);
|
|
||||||
Object.assign(config, JSON.parse(data));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const saveConfig = async (): Promise<void> => {
|
export const saveConfig = async (): Promise<void> => {
|
||||||
amnesia = true;
|
amnesia = true;
|
||||||
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
|
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const syncConfigWithDatabase = (): void => {
|
||||||
|
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
|
||||||
|
if (!config.worldState?.galleonOfGhouls) {
|
||||||
|
void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {}); // For some reason, I can't just do `Inbox.deleteMany(...)`; it needs this whole circus.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -349,7 +349,8 @@ export const removeDojoDeco = (
|
|||||||
component.DecoCapacity! += meta.capacityCost;
|
component.DecoCapacity! += meta.capacityCost;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
|
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
|
||||||
|
component.DecoCapacity! += meta.dojoCapacityCost!;
|
||||||
if (deco.Sockets !== undefined) {
|
if (deco.Sockets !== undefined) {
|
||||||
addVaultFusionTreasures(guild, [
|
addVaultFusionTreasures(guild, [
|
||||||
{
|
{
|
||||||
@ -549,6 +550,19 @@ export const processFundedGuildTechProject = (
|
|||||||
guild.XP += recipe.guildXpValue;
|
guild.XP += recipe.guildXpValue;
|
||||||
}
|
}
|
||||||
setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
|
setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
|
||||||
|
if (config.noDojoResearchTime) {
|
||||||
|
processCompletedGuildTechProject(guild, techProject.ItemType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const processCompletedGuildTechProject = (guild: TGuildDatabaseDocument, type: string): void => {
|
||||||
|
if (type.startsWith("/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/")) {
|
||||||
|
guild.VaultDecoRecipes ??= [];
|
||||||
|
guild.VaultDecoRecipes.push({
|
||||||
|
ItemType: type,
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setGuildTechLogState = (
|
export const setGuildTechLogState = (
|
||||||
|
@ -296,6 +296,12 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
|
|||||||
db[key] = client[key];
|
db[key] = client[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// IRewardAtten[]
|
||||||
|
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
|
||||||
|
if (client[key] !== undefined) {
|
||||||
|
db[key] = client[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (client.XPInfo !== undefined) {
|
if (client.XPInfo !== undefined) {
|
||||||
db.XPInfo = client.XPInfo;
|
db.XPInfo = client.XPInfo;
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ import { IMessageDatabase, Inbox } from "@/src/models/inboxModel";
|
|||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { HydratedDocument, Types } from "mongoose";
|
import { HydratedDocument, Types } from "mongoose";
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import eventMessages from "@/static/fixed_responses/eventMessages.json";
|
import { unixTimesInMs } from "../constants/timeConstants";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { config } from "./configService";
|
||||||
|
|
||||||
export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => {
|
export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => {
|
||||||
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });
|
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });
|
||||||
@ -29,37 +29,72 @@ export const deleteAllMessagesRead = async (accountId: string): Promise<void> =>
|
|||||||
|
|
||||||
export const createNewEventMessages = async (req: Request): Promise<void> => {
|
export const createNewEventMessages = async (req: Request): Promise<void> => {
|
||||||
const account = await getAccountForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
const latestEventMessageDate = account.LatestEventMessageDate;
|
const newEventMessages: IMessageCreationTemplate[] = [];
|
||||||
|
|
||||||
//TODO: is baroo there? create these kind of messages too (periodical messages)
|
// Baro
|
||||||
const newEventMessages = eventMessages.Messages.filter(m => new Date(m.eventMessageDate) > latestEventMessageDate);
|
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
|
||||||
|
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
|
||||||
|
const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
|
||||||
|
if (account.LatestEventMessageDate.getTime() < baroActualStart) {
|
||||||
|
newEventMessages.push({
|
||||||
|
sndr: "/Lotus/Language/G1Quests/VoidTraderName",
|
||||||
|
sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle",
|
||||||
|
msg: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceMessage",
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/BaroKiTeerPortrait.png",
|
||||||
|
startDate: new Date(baroActualStart),
|
||||||
|
endDate: new Date(baroStart + unixTimesInMs.day * 14),
|
||||||
|
CrossPlatform: true,
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "NODE_NAME",
|
||||||
|
Tag: ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
date: new Date(baroActualStart)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
|
||||||
|
if (config.worldState?.galleonOfGhouls) {
|
||||||
|
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
|
||||||
|
newEventMessages.push({
|
||||||
|
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
|
||||||
|
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
|
||||||
|
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
|
||||||
|
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
|
||||||
|
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
|
||||||
|
startDate: new Date(),
|
||||||
|
goalTag: "GalleonRobbery"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (newEventMessages.length === 0) {
|
if (newEventMessages.length === 0) {
|
||||||
logger.debug(`No new event messages. Latest event message date: ${latestEventMessageDate.toISOString()}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedEventMessages = await createMessage(account._id, newEventMessages);
|
await createMessage(account._id, newEventMessages);
|
||||||
logger.debug("created event messages", savedEventMessages);
|
|
||||||
|
|
||||||
const latestEventMessage = newEventMessages.reduce((prev, current) =>
|
const latestEventMessage = newEventMessages.reduce((prev, current) =>
|
||||||
prev.eventMessageDate > current.eventMessageDate ? prev : current
|
prev.startDate! > current.startDate! ? prev : current
|
||||||
);
|
);
|
||||||
|
account.LatestEventMessageDate = new Date(latestEventMessage.startDate!);
|
||||||
account.LatestEventMessageDate = new Date(latestEventMessage.eventMessageDate);
|
|
||||||
await account.save();
|
await account.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createMessage = async (accountId: string | Types.ObjectId, messages: IMessageCreationTemplate[]) => {
|
export const createMessage = async (
|
||||||
|
accountId: string | Types.ObjectId,
|
||||||
|
messages: IMessageCreationTemplate[]
|
||||||
|
): Promise<void> => {
|
||||||
const ownerIdMessages = messages.map(m => ({
|
const ownerIdMessages = messages.map(m => ({
|
||||||
...m,
|
...m,
|
||||||
|
date: m.date ?? new Date(),
|
||||||
ownerId: accountId
|
ownerId: accountId
|
||||||
}));
|
}));
|
||||||
|
await Inbox.insertMany(ownerIdMessages);
|
||||||
const savedMessages = await Inbox.insertMany(ownerIdMessages);
|
|
||||||
return savedMessages;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IMessageCreationTemplate extends Omit<IMessageDatabase, "_id" | "date" | "ownerId"> {
|
export interface IMessageCreationTemplate extends Omit<IMessageDatabase, "_id" | "date" | "ownerId"> {
|
||||||
ownerId?: string;
|
date?: Date;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,8 @@ import {
|
|||||||
ICalendarProgress,
|
ICalendarProgress,
|
||||||
INemesisWeaponTargetFingerprint,
|
INemesisWeaponTargetFingerprint,
|
||||||
INemesisPetTargetFingerprint,
|
INemesisPetTargetFingerprint,
|
||||||
IDialogueDatabase
|
IDialogueDatabase,
|
||||||
|
IKubrowPetPrintClient
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
|
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
|
||||||
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
|
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
|
||||||
@ -43,6 +44,7 @@ import {
|
|||||||
} from "../types/inventoryTypes/commonInventoryTypes";
|
} from "../types/inventoryTypes/commonInventoryTypes";
|
||||||
import {
|
import {
|
||||||
ExportArcanes,
|
ExportArcanes,
|
||||||
|
ExportBoosters,
|
||||||
ExportBundles,
|
ExportBundles,
|
||||||
ExportChallenges,
|
ExportChallenges,
|
||||||
ExportCustoms,
|
ExportCustoms,
|
||||||
@ -81,12 +83,14 @@ import { addQuestKey, completeQuest } from "@/src/services/questService";
|
|||||||
import { handleBundleAcqusition } from "./purchaseService";
|
import { handleBundleAcqusition } from "./purchaseService";
|
||||||
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
||||||
import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService";
|
import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService";
|
||||||
import { createMessage } from "./inboxService";
|
import { createMessage, IMessageCreationTemplate } from "./inboxService";
|
||||||
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper";
|
||||||
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
|
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
|
||||||
|
import { ICalendarSeason } from "@/src/types/worldStateTypes";
|
||||||
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
|
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
|
||||||
import { TAccountDocument } from "./loginService";
|
import { TAccountDocument } from "./loginService";
|
||||||
import { unixTimesInMs } from "../constants/timeConstants";
|
import { unixTimesInMs } from "../constants/timeConstants";
|
||||||
|
import { addString } from "../helpers/stringHelpers";
|
||||||
|
|
||||||
export const createInventory = async (
|
export const createInventory = async (
|
||||||
accountOwnerId: Types.ObjectId,
|
accountOwnerId: Types.ObjectId,
|
||||||
@ -422,7 +426,6 @@ export const addItem = async (
|
|||||||
ItemType: "/Lotus/Types/Game/KubrowPet/Eggs/KubrowEgg",
|
ItemType: "/Lotus/Types/Game/KubrowPet/Eggs/KubrowEgg",
|
||||||
_id: new Types.ObjectId()
|
_id: new Types.ObjectId()
|
||||||
};
|
};
|
||||||
inventory.KubrowPetEggs ??= [];
|
|
||||||
inventory.KubrowPetEggs.push(egg);
|
inventory.KubrowPetEggs.push(egg);
|
||||||
changes.push({
|
changes.push({
|
||||||
ItemType: egg.ItemType,
|
ItemType: egg.ItemType,
|
||||||
@ -497,6 +500,7 @@ export const addItem = async (
|
|||||||
// - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity.
|
// - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity.
|
||||||
if (!exactQuantity) {
|
if (!exactQuantity) {
|
||||||
quantity *= ExportGear[typeName].purchaseQuantity ?? 1;
|
quantity *= ExportGear[typeName].purchaseQuantity ?? 1;
|
||||||
|
logger.debug(`non-exact acquisition of ${typeName}; factored quantity is ${quantity}`);
|
||||||
}
|
}
|
||||||
const consumablesChanges = [
|
const consumablesChanges = [
|
||||||
{
|
{
|
||||||
@ -668,6 +672,17 @@ export const addItem = async (
|
|||||||
return await addEmailItem(inventory, typeName);
|
return await addEmailItem(inventory, typeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Boosters are an odd case. They're only added like this via Baro's Void Surplus afaik.
|
||||||
|
{
|
||||||
|
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == typeName);
|
||||||
|
if (boosterEntry) {
|
||||||
|
addBooster(typeName, quantity, inventory);
|
||||||
|
return {
|
||||||
|
Boosters: [{ ItemType: typeName, ExpiryDate: quantity }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Path-based duck typing
|
// Path-based duck typing
|
||||||
switch (typeName.substr(1).split("/")[1]) {
|
switch (typeName.substr(1).split("/")[1]) {
|
||||||
case "Powersuits":
|
case "Powersuits":
|
||||||
@ -781,7 +796,11 @@ export const addItem = async (
|
|||||||
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
|
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
|
||||||
typeName.substr(1).split("/")[3] == "KubrowPet"
|
typeName.substr(1).split("/")[3] == "KubrowPet"
|
||||||
) {
|
) {
|
||||||
if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
|
if (
|
||||||
|
typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem" &&
|
||||||
|
typeName != "/Lotus/Types/Game/KubrowPet/BlankTraitPrint" &&
|
||||||
|
typeName != "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"
|
||||||
|
) {
|
||||||
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
|
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
|
||||||
}
|
}
|
||||||
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
|
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
|
||||||
@ -1045,8 +1064,13 @@ export const addKubrowPet = (
|
|||||||
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
|
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
|
||||||
|
|
||||||
if (!details) {
|
if (!details) {
|
||||||
let traits: ITraits;
|
const isCatbrow = [
|
||||||
|
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit",
|
||||||
|
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
|
||||||
|
"/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit"
|
||||||
|
].includes(kubrowPetName);
|
||||||
|
|
||||||
|
let traits: ITraits;
|
||||||
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
|
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
|
||||||
traits = {
|
traits = {
|
||||||
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
|
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
|
||||||
@ -1061,12 +1085,7 @@ export const addKubrowPet = (
|
|||||||
Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
|
Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const isCatbrow = [
|
|
||||||
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
|
|
||||||
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit"
|
|
||||||
].includes(kubrowPetName);
|
|
||||||
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
|
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
|
||||||
|
|
||||||
traits = {
|
traits = {
|
||||||
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
||||||
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
|
||||||
@ -1085,7 +1104,7 @@ export const addKubrowPet = (
|
|||||||
Name: "",
|
Name: "",
|
||||||
IsPuppy: !premiumPurchase,
|
IsPuppy: !premiumPurchase,
|
||||||
HasCollar: true,
|
HasCollar: true,
|
||||||
PrintsRemaining: 3,
|
PrintsRemaining: isCatbrow ? 3 : 2,
|
||||||
Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating,
|
Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating,
|
||||||
HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
|
HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
|
||||||
IsMale: !!getRandomInt(0, 1),
|
IsMale: !!getRandomInt(0, 1),
|
||||||
@ -1109,6 +1128,26 @@ export const addKubrowPet = (
|
|||||||
return inventoryChanges;
|
return inventoryChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const addKubrowPetPrint = (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
pet: IEquipmentDatabase,
|
||||||
|
inventoryChanges: IInventoryChanges
|
||||||
|
): void => {
|
||||||
|
inventoryChanges.KubrowPetPrints ??= [];
|
||||||
|
inventoryChanges.KubrowPetPrints.push(
|
||||||
|
inventory.KubrowPetPrints[
|
||||||
|
inventory.KubrowPetPrints.push({
|
||||||
|
ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint",
|
||||||
|
Name: pet.Details!.Name,
|
||||||
|
IsMale: pet.Details!.IsMale,
|
||||||
|
Size: pet.Details!.Size,
|
||||||
|
DominantTraits: pet.Details!.DominantTraits,
|
||||||
|
RecessiveTraits: pet.Details!.RecessiveTraits
|
||||||
|
}) - 1
|
||||||
|
].toJSON<IKubrowPetPrintClient>()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const updateSlots = (
|
export const updateSlots = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
slotName: SlotNames,
|
slotName: SlotNames,
|
||||||
@ -1202,8 +1241,10 @@ export const addStanding = (
|
|||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
syndicateTag: string,
|
syndicateTag: string,
|
||||||
gainedStanding: number,
|
gainedStanding: number,
|
||||||
isMedallion: boolean = false
|
affiliationMods: IAffiliationMods[] = [],
|
||||||
): IAffiliationMods => {
|
isMedallion: boolean = false,
|
||||||
|
propagateAlignments: boolean = true
|
||||||
|
): void => {
|
||||||
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
|
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
|
||||||
const syndicateMeta = ExportSyndicates[syndicateTag];
|
const syndicateMeta = ExportSyndicates[syndicateTag];
|
||||||
|
|
||||||
@ -1215,6 +1256,10 @@ export const addStanding = (
|
|||||||
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
||||||
if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing;
|
if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing;
|
||||||
|
|
||||||
|
if (syndicate.Title == -2 && syndicate.Standing + gainedStanding < -71000) {
|
||||||
|
gainedStanding = -71000 + syndicate.Standing;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
|
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
|
||||||
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
||||||
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
|
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
|
||||||
@ -1223,10 +1268,27 @@ export const addStanding = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
syndicate.Standing += gainedStanding;
|
syndicate.Standing += gainedStanding;
|
||||||
return {
|
const affiliationMod: IAffiliationMods = {
|
||||||
Tag: syndicateTag,
|
Tag: syndicateTag,
|
||||||
Standing: gainedStanding
|
Standing: gainedStanding
|
||||||
};
|
};
|
||||||
|
affiliationMods.push(affiliationMod);
|
||||||
|
|
||||||
|
if (syndicateMeta.alignments) {
|
||||||
|
if (propagateAlignments) {
|
||||||
|
for (const [tag, factor] of Object.entries(syndicateMeta.alignments)) {
|
||||||
|
addStanding(inventory, tag, gainedStanding * factor, affiliationMods, isMedallion, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (syndicate.Standing < getMinStanding(syndicateMeta, syndicate.Title ?? 0)) {
|
||||||
|
syndicate.Title ??= 0;
|
||||||
|
syndicate.Title -= 1;
|
||||||
|
affiliationMod.Title ??= 0;
|
||||||
|
affiliationMod.Title -= 1;
|
||||||
|
logger.debug(`${syndicateTag} is decreasing to title ${syndicate.Title} after applying alignment`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: AffiliationMods support (Nightwave).
|
// TODO: AffiliationMods support (Nightwave).
|
||||||
@ -1304,7 +1366,7 @@ export const addCustomization = (
|
|||||||
customizationName: string,
|
customizationName: string,
|
||||||
inventoryChanges: IInventoryChanges = {}
|
inventoryChanges: IInventoryChanges = {}
|
||||||
): IInventoryChanges => {
|
): IInventoryChanges => {
|
||||||
if (!inventory.FlavourItems.find(x => x.ItemType == customizationName)) {
|
if (!inventory.FlavourItems.some(x => x.ItemType == customizationName)) {
|
||||||
const flavourItemIndex = inventory.FlavourItems.push({ ItemType: customizationName }) - 1;
|
const flavourItemIndex = inventory.FlavourItems.push({ ItemType: customizationName }) - 1;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
inventoryChanges.FlavourItems ??= [];
|
inventoryChanges.FlavourItems ??= [];
|
||||||
@ -1320,7 +1382,7 @@ export const addSkin = (
|
|||||||
typeName: string,
|
typeName: string,
|
||||||
inventoryChanges: IInventoryChanges = {}
|
inventoryChanges: IInventoryChanges = {}
|
||||||
): IInventoryChanges => {
|
): IInventoryChanges => {
|
||||||
if (inventory.WeaponSkins.find(x => x.ItemType == typeName)) {
|
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) {
|
||||||
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
|
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
|
||||||
} else {
|
} else {
|
||||||
const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1;
|
const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1;
|
||||||
@ -1501,7 +1563,22 @@ export const addEmailItem = async (
|
|||||||
const meta = ExportEmailItems[typeName];
|
const meta = ExportEmailItems[typeName];
|
||||||
const emailItem = inventory.EmailItems.find(x => x.ItemType == typeName);
|
const emailItem = inventory.EmailItems.find(x => x.ItemType == typeName);
|
||||||
if (!emailItem || !meta.sendOnlyOnce) {
|
if (!emailItem || !meta.sendOnlyOnce) {
|
||||||
await createMessage(inventory.accountOwnerId, [convertInboxMessage(meta.message)]);
|
const msg: IMessageCreationTemplate = convertInboxMessage(meta.message);
|
||||||
|
if (msg.cinematic == "/Lotus/Levels/1999/PlayerHomeBalconyCinematics.level") {
|
||||||
|
msg.customData = JSON.stringify({
|
||||||
|
Tag: msg.customData + "KissCin",
|
||||||
|
CinLoadout: {
|
||||||
|
Skins: inventory.AdultOperatorLoadOuts[0].Skins,
|
||||||
|
Upgrades: inventory.AdultOperatorLoadOuts[0].Upgrades,
|
||||||
|
attcol: inventory.AdultOperatorLoadOuts[0].attcol,
|
||||||
|
cloth: inventory.AdultOperatorLoadOuts[0].cloth,
|
||||||
|
eyecol: inventory.AdultOperatorLoadOuts[0].eyecol,
|
||||||
|
pricol: inventory.AdultOperatorLoadOuts[0].pricol,
|
||||||
|
syancol: inventory.AdultOperatorLoadOuts[0].syancol
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await createMessage(inventory.accountOwnerId, [msg]);
|
||||||
|
|
||||||
if (emailItem) {
|
if (emailItem) {
|
||||||
emailItem.ItemCount += 1;
|
emailItem.ItemCount += 1;
|
||||||
@ -1557,7 +1634,7 @@ export const addMiscItem = (
|
|||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
type: string,
|
type: string,
|
||||||
count: number,
|
count: number,
|
||||||
inventoryChanges: IInventoryChanges
|
inventoryChanges: IInventoryChanges = {}
|
||||||
): void => {
|
): void => {
|
||||||
const miscItemChanges: IMiscItem[] = [
|
const miscItemChanges: IMiscItem[] = [
|
||||||
{
|
{
|
||||||
@ -1708,12 +1785,27 @@ export const addFocusXpIncreases = (inventory: TInventoryDatabaseDocument, focus
|
|||||||
AP_ANY
|
AP_ANY
|
||||||
}
|
}
|
||||||
|
|
||||||
inventory.FocusXP ??= { AP_ATTACK: 0, AP_DEFENSE: 0, AP_TACTIC: 0, AP_POWER: 0, AP_WARD: 0 };
|
inventory.FocusXP ??= {};
|
||||||
inventory.FocusXP.AP_ATTACK += focusXpPlus[FocusType.AP_ATTACK];
|
if (focusXpPlus[FocusType.AP_ATTACK]) {
|
||||||
inventory.FocusXP.AP_DEFENSE += focusXpPlus[FocusType.AP_DEFENSE];
|
inventory.FocusXP.AP_ATTACK ??= 0;
|
||||||
inventory.FocusXP.AP_TACTIC += focusXpPlus[FocusType.AP_TACTIC];
|
inventory.FocusXP.AP_ATTACK += focusXpPlus[FocusType.AP_ATTACK];
|
||||||
inventory.FocusXP.AP_POWER += focusXpPlus[FocusType.AP_POWER];
|
}
|
||||||
inventory.FocusXP.AP_WARD += focusXpPlus[FocusType.AP_WARD];
|
if (focusXpPlus[FocusType.AP_DEFENSE]) {
|
||||||
|
inventory.FocusXP.AP_DEFENSE ??= 0;
|
||||||
|
inventory.FocusXP.AP_DEFENSE += focusXpPlus[FocusType.AP_DEFENSE];
|
||||||
|
}
|
||||||
|
if (focusXpPlus[FocusType.AP_TACTIC]) {
|
||||||
|
inventory.FocusXP.AP_TACTIC ??= 0;
|
||||||
|
inventory.FocusXP.AP_TACTIC += focusXpPlus[FocusType.AP_TACTIC];
|
||||||
|
}
|
||||||
|
if (focusXpPlus[FocusType.AP_POWER]) {
|
||||||
|
inventory.FocusXP.AP_POWER ??= 0;
|
||||||
|
inventory.FocusXP.AP_POWER += focusXpPlus[FocusType.AP_POWER];
|
||||||
|
}
|
||||||
|
if (focusXpPlus[FocusType.AP_WARD]) {
|
||||||
|
inventory.FocusXP.AP_WARD ??= 0;
|
||||||
|
inventory.FocusXP.AP_WARD += focusXpPlus[FocusType.AP_WARD];
|
||||||
|
}
|
||||||
|
|
||||||
if (!config.noDailyFocusLimit) {
|
if (!config.noDailyFocusLimit) {
|
||||||
inventory.DailyFocus -= focusXpPlus.reduce((a, b) => a + b, 0);
|
inventory.DailyFocus -= focusXpPlus.reduce((a, b) => a + b, 0);
|
||||||
@ -1745,6 +1837,10 @@ export const addChallenges = (
|
|||||||
} else {
|
} else {
|
||||||
inventory.ChallengeProgress.push({ Name, Progress });
|
inventory.ChallengeProgress.push({ Name, Progress });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Name.startsWith("Calendar")) {
|
||||||
|
addString(getCalendarProgress(inventory).SeasonProgress.ActivatedChallenges, Name);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const affiliationMods: IAffiliationMods[] = [];
|
const affiliationMods: IAffiliationMods[] = [];
|
||||||
@ -1787,12 +1883,24 @@ export const addChallenges = (
|
|||||||
return affiliationMods;
|
return affiliationMods;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes }: IMission): void => {
|
export const addCalendarProgress = (inventory: TInventoryDatabaseDocument, value: { challenge: string }[]): void => {
|
||||||
|
const calendarProgress = getCalendarProgress(inventory);
|
||||||
|
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
||||||
|
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
|
||||||
|
day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge
|
||||||
|
);
|
||||||
|
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => {
|
||||||
const { Missions } = inventory;
|
const { Missions } = inventory;
|
||||||
const itemIndex = Missions.findIndex(item => item.Tag === Tag);
|
const itemIndex = Missions.findIndex(item => item.Tag === Tag);
|
||||||
|
|
||||||
if (itemIndex !== -1) {
|
if (itemIndex !== -1) {
|
||||||
Missions[itemIndex].Completes += Completes;
|
Missions[itemIndex].Completes += Completes;
|
||||||
|
if (Tier) {
|
||||||
|
Missions[itemIndex].Tier = Tier;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Missions.push({ Tag, Completes });
|
Missions.push({ Tag, Completes });
|
||||||
}
|
}
|
||||||
@ -1988,6 +2096,20 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
|
|||||||
return inventory.CalendarProgress;
|
return inventory.CalendarProgress;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkCalendarChallengeCompletion = (
|
||||||
|
calendarProgress: ICalendarProgress,
|
||||||
|
currentSeason: ICalendarSeason
|
||||||
|
): void => {
|
||||||
|
const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
|
||||||
|
if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx >= dayIndex) {
|
||||||
|
const day = currentSeason.Days[dayIndex];
|
||||||
|
if (day.events.length != 0 && day.events[0].type == "CET_CHALLENGE") {
|
||||||
|
//logger.debug(`already completed the challenge, skipping ahead`);
|
||||||
|
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const giveNemesisWeaponRecipe = (
|
export const giveNemesisWeaponRecipe = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
weaponType: string,
|
weaponType: string,
|
||||||
|
@ -45,6 +45,39 @@ export type WeaponTypeInternal =
|
|||||||
| "SpecialItems";
|
| "SpecialItems";
|
||||||
|
|
||||||
export const getRecipe = (uniqueName: string): IRecipe | undefined => {
|
export const getRecipe = (uniqueName: string): IRecipe | undefined => {
|
||||||
|
// Handle crafting of archwing summon for versions prior to 39.0.0 as this blueprint was removed then.
|
||||||
|
if (uniqueName == "/Lotus/Types/Recipes/EidolonRecipes/OpenArchwingSummonBlueprint") {
|
||||||
|
return {
|
||||||
|
resultType: "/Lotus/Types/Restoratives/OpenArchwingSummon",
|
||||||
|
buildPrice: 7500,
|
||||||
|
buildTime: 1800,
|
||||||
|
skipBuildTimePrice: 10,
|
||||||
|
consumeOnUse: false,
|
||||||
|
num: 1,
|
||||||
|
codexSecret: false,
|
||||||
|
alwaysAvailable: true,
|
||||||
|
ingredients: [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/IraditeItem",
|
||||||
|
ItemCount: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem",
|
||||||
|
ItemCount: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Items/Fish/Eidolon/FishParts/EidolonFishOilItem",
|
||||||
|
ItemCount: 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Items/MiscItems/Circuits",
|
||||||
|
ItemCount: 600
|
||||||
|
}
|
||||||
|
],
|
||||||
|
excludeFromMarket: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return ExportRecipes[uniqueName];
|
return ExportRecipes[uniqueName];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -218,7 +251,9 @@ export const convertInboxMessage = (message: IInboxMessage): IMessage => {
|
|||||||
return {
|
return {
|
||||||
sndr: message.sender,
|
sndr: message.sender,
|
||||||
msg: message.body,
|
msg: message.body,
|
||||||
|
cinematic: message.cinematic,
|
||||||
sub: message.title,
|
sub: message.title,
|
||||||
|
customData: message.customData,
|
||||||
att: message.attachments.length > 0 ? message.attachments : undefined,
|
att: message.attachments.length > 0 ? message.attachments : undefined,
|
||||||
countedAtt: message.countedAttachments.length > 0 ? message.countedAttachments : undefined,
|
countedAtt: message.countedAttachments.length > 0 ? message.countedAttachments : undefined,
|
||||||
icon: message.icon ?? "",
|
icon: message.icon ?? "",
|
||||||
|
@ -144,7 +144,8 @@ export const claimLoginReward = async (
|
|||||||
case "RT_STORE_ITEM":
|
case "RT_STORE_ITEM":
|
||||||
case "RT_RECIPE":
|
case "RT_RECIPE":
|
||||||
case "RT_RANDOM_RECIPE":
|
case "RT_RANDOM_RECIPE":
|
||||||
return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount)).InventoryChanges;
|
return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount, undefined, true))
|
||||||
|
.InventoryChanges;
|
||||||
|
|
||||||
case "RT_CREDITS":
|
case "RT_CREDITS":
|
||||||
return updateCurrency(inventory, -reward.Amount, false);
|
return updateCurrency(inventory, -reward.Amount, false);
|
||||||
|
@ -18,6 +18,23 @@ export const isNameTaken = async (name: string): Promise<boolean> => {
|
|||||||
return !!(await Account.findOne({ DisplayName: name }));
|
return !!(await Account.findOne({ DisplayName: name }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createNonce = (): number => {
|
||||||
|
return Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUsernameFromEmail = async (email: string): Promise<string> => {
|
||||||
|
const nameFromEmail = email.substring(0, email.indexOf("@"));
|
||||||
|
let name = nameFromEmail || email.substring(1) || "SpaceNinja";
|
||||||
|
if (await isNameTaken(name)) {
|
||||||
|
let suffix = 0;
|
||||||
|
do {
|
||||||
|
++suffix;
|
||||||
|
name = nameFromEmail + suffix;
|
||||||
|
} while (await isNameTaken(name));
|
||||||
|
}
|
||||||
|
return nameFromEmail;
|
||||||
|
};
|
||||||
|
|
||||||
export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise<IDatabaseAccountJson> => {
|
export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise<IDatabaseAccountJson> => {
|
||||||
const account = new Account(accountData);
|
const account = new Account(accountData);
|
||||||
try {
|
try {
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
ExportEnemies,
|
ExportEnemies,
|
||||||
ExportFusionBundles,
|
ExportFusionBundles,
|
||||||
ExportRegions,
|
ExportRegions,
|
||||||
|
ExportRelics,
|
||||||
ExportRewards,
|
ExportRewards,
|
||||||
IMissionReward as IMissionRewardExternal,
|
IMissionReward as IMissionRewardExternal,
|
||||||
IRegion,
|
IRegion,
|
||||||
@ -13,6 +14,7 @@ import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/servi
|
|||||||
import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import {
|
import {
|
||||||
addBooster,
|
addBooster,
|
||||||
|
addCalendarProgress,
|
||||||
addChallenges,
|
addChallenges,
|
||||||
addConsumables,
|
addConsumables,
|
||||||
addCrewShipAmmo,
|
addCrewShipAmmo,
|
||||||
@ -34,7 +36,6 @@ import {
|
|||||||
applyClientEquipmentUpdates,
|
applyClientEquipmentUpdates,
|
||||||
combineInventoryChanges,
|
combineInventoryChanges,
|
||||||
generateRewardSeed,
|
generateRewardSeed,
|
||||||
getCalendarProgress,
|
|
||||||
getDialogue,
|
getDialogue,
|
||||||
giveNemesisPetRecipe,
|
giveNemesisPetRecipe,
|
||||||
giveNemesisWeaponRecipe,
|
giveNemesisWeaponRecipe,
|
||||||
@ -44,7 +45,7 @@ import {
|
|||||||
import { updateQuestKey } from "@/src/services/questService";
|
import { updateQuestKey } from "@/src/services/questService";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { fromStoreItem, getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService";
|
import { fromStoreItem, getLevelKeyRewards, isStoreItem, toStoreItem } from "@/src/services/itemDataService";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
|
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
@ -86,7 +87,7 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
|
|||||||
if (rewardInfo.VaultsCracked) {
|
if (rewardInfo.VaultsCracked) {
|
||||||
const rotations: number[] = [];
|
const rotations: number[] = [];
|
||||||
for (let i = 0; i != rewardInfo.VaultsCracked; ++i) {
|
for (let i = 0; i != rewardInfo.VaultsCracked; ++i) {
|
||||||
rotations.push(i);
|
rotations.push(Math.min(i, 2));
|
||||||
}
|
}
|
||||||
return rotations;
|
return rotations;
|
||||||
}
|
}
|
||||||
@ -233,7 +234,7 @@ export const addMissionInventoryUpdates = async (
|
|||||||
}
|
}
|
||||||
for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) {
|
for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
logger.error(`Inventory update key ${key} has no value `);
|
logger.error(`Inventory update key ${key} has no value`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@ -266,7 +267,9 @@ export const addMissionInventoryUpdates = async (
|
|||||||
addMissionComplete(inventory, value);
|
addMissionComplete(inventory, value);
|
||||||
break;
|
break;
|
||||||
case "LastRegionPlayed":
|
case "LastRegionPlayed":
|
||||||
inventory.LastRegionPlayed = value;
|
if (!(config.unfaithfulBugFixes?.ignore1999LastRegionPlayed && value === "1999MapName")) {
|
||||||
|
inventory.LastRegionPlayed = value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "RawUpgrades":
|
case "RawUpgrades":
|
||||||
addMods(inventory, value);
|
addMods(inventory, value);
|
||||||
@ -478,7 +481,7 @@ export const addMissionInventoryUpdates = async (
|
|||||||
msg: "/Lotus/Language/G1Quests/DeathMarkMessage",
|
msg: "/Lotus/Language/G1Quests/DeathMarkMessage",
|
||||||
icon: "/Lotus/Interface/Icons/Npcs/Stalker_d.png",
|
icon: "/Lotus/Interface/Icons/Npcs/Stalker_d.png",
|
||||||
highPriority: true,
|
highPriority: true,
|
||||||
expiry: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's clear if this is correct.
|
endDate: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's not clear if this is correct.
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -522,7 +525,6 @@ export const addMissionInventoryUpdates = async (
|
|||||||
}
|
}
|
||||||
case "KubrowPetEggs": {
|
case "KubrowPetEggs": {
|
||||||
for (const egg of value) {
|
for (const egg of value) {
|
||||||
inventory.KubrowPetEggs ??= [];
|
|
||||||
inventory.KubrowPetEggs.push({
|
inventory.KubrowPetEggs.push({
|
||||||
ItemType: egg.ItemType,
|
ItemType: egg.ItemType,
|
||||||
_id: new Types.ObjectId()
|
_id: new Types.ObjectId()
|
||||||
@ -605,6 +607,47 @@ export const addMissionInventoryUpdates = async (
|
|||||||
inventoryChanges.RegularCredits -= value;
|
inventoryChanges.RegularCredits -= value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "GoalProgress": {
|
||||||
|
for (const uploadProgress of value) {
|
||||||
|
const goal = getWorldState().Goals.find(x => x._id.$oid == uploadProgress._id.$oid);
|
||||||
|
if (goal && goal.Personal) {
|
||||||
|
inventory.PersonalGoalProgress ??= [];
|
||||||
|
const goalProgress = inventory.PersonalGoalProgress.find(x => x.goalId.equals(goal._id.$oid));
|
||||||
|
if (goalProgress) {
|
||||||
|
goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
|
||||||
|
goalProgress.Count += uploadProgress.Count;
|
||||||
|
} else {
|
||||||
|
inventory.PersonalGoalProgress.push({
|
||||||
|
Best: uploadProgress.Best,
|
||||||
|
Count: uploadProgress.Count,
|
||||||
|
Tag: goal.Tag,
|
||||||
|
goalId: new Types.ObjectId(goal._id.$oid)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
goal.Reward &&
|
||||||
|
goal.Reward.items &&
|
||||||
|
goal.MissionKeyName &&
|
||||||
|
goal.MissionKeyName in goalMessagesByKey
|
||||||
|
) {
|
||||||
|
// Send reward via inbox
|
||||||
|
const info = goalMessagesByKey[goal.MissionKeyName];
|
||||||
|
await createMessage(inventory.accountOwnerId, [
|
||||||
|
{
|
||||||
|
sndr: info.sndr,
|
||||||
|
msg: info.msg,
|
||||||
|
att: goal.Reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x)),
|
||||||
|
sub: info.sub,
|
||||||
|
icon: info.icon,
|
||||||
|
highPriority: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "InvasionProgress": {
|
case "InvasionProgress": {
|
||||||
for (const clientProgress of value) {
|
for (const clientProgress of value) {
|
||||||
const dbProgress = inventory.QualifyingInvasions.find(x =>
|
const dbProgress = inventory.QualifyingInvasions.find(x =>
|
||||||
@ -626,12 +669,7 @@ export const addMissionInventoryUpdates = async (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "CalendarProgress": {
|
case "CalendarProgress": {
|
||||||
const calendarProgress = getCalendarProgress(inventory);
|
addCalendarProgress(inventory, value);
|
||||||
for (const progress of value) {
|
|
||||||
const challengeName = progress.challenge.substring(progress.challenge.lastIndexOf("/") + 1);
|
|
||||||
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx++;
|
|
||||||
calendarProgress.SeasonProgress.ActivatedChallenges.push(challengeName);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "duviriCaveOffers": {
|
case "duviriCaveOffers": {
|
||||||
@ -923,6 +961,7 @@ const droptableAliases: Record<string, string> = {
|
|||||||
|
|
||||||
//TODO: return type of partial missioninventoryupdate response
|
//TODO: return type of partial missioninventoryupdate response
|
||||||
export const addMissionRewards = async (
|
export const addMissionRewards = async (
|
||||||
|
account: TAccountDocument,
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
{
|
{
|
||||||
wagerTier: wagerTier,
|
wagerTier: wagerTier,
|
||||||
@ -958,17 +997,29 @@ export const addMissionRewards = async (
|
|||||||
|
|
||||||
let missionCompletionCredits = 0;
|
let missionCompletionCredits = 0;
|
||||||
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
|
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
|
||||||
|
|
||||||
|
if (rewardInfo.goalId) {
|
||||||
|
const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId);
|
||||||
|
if (goal?.MissionKeyName) {
|
||||||
|
levelKeyName = goal.MissionKeyName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (levelKeyName) {
|
if (levelKeyName) {
|
||||||
const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
|
const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
|
||||||
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
|
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
|
||||||
if (fixedLevelRewards.levelKeyRewards) {
|
if (fixedLevelRewards.levelKeyRewards) {
|
||||||
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards, rewardInfo);
|
missionCompletionCredits += addFixedLevelRewards(
|
||||||
|
fixedLevelRewards.levelKeyRewards,
|
||||||
|
MissionRewards,
|
||||||
|
rewardInfo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (fixedLevelRewards.levelKeyRewards2) {
|
if (fixedLevelRewards.levelKeyRewards2) {
|
||||||
for (const reward of fixedLevelRewards.levelKeyRewards2) {
|
for (const reward of fixedLevelRewards.levelKeyRewards2) {
|
||||||
//quest stage completion credit rewards
|
//quest stage completion credit rewards
|
||||||
if (reward.rewardType == "RT_CREDITS") {
|
if (reward.rewardType == "RT_CREDITS") {
|
||||||
missionCompletionCredits += reward.amount; // will be added to inventory in addCredits
|
missionCompletionCredits += reward.amount;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
MissionRewards.push({
|
MissionRewards.push({
|
||||||
@ -997,12 +1048,11 @@ export const addMissionRewards = async (
|
|||||||
) {
|
) {
|
||||||
const levelCreditReward = getLevelCreditRewards(node);
|
const levelCreditReward = getLevelCreditRewards(node);
|
||||||
missionCompletionCredits += levelCreditReward;
|
missionCompletionCredits += levelCreditReward;
|
||||||
inventory.RegularCredits += levelCreditReward;
|
|
||||||
logger.debug(`levelCreditReward ${levelCreditReward}`);
|
logger.debug(`levelCreditReward ${levelCreditReward}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.missionReward) {
|
if (node.missionReward) {
|
||||||
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo);
|
missionCompletionCredits += addFixedLevelRewards(node.missionReward, MissionRewards, rewardInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rewardInfo.sortieTag == "Mission1") {
|
if (rewardInfo.sortieTag == "Mission1") {
|
||||||
@ -1112,7 +1162,9 @@ export const addMissionRewards = async (
|
|||||||
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
|
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
const credits = addCredits(inventory, {
|
inventory.RegularCredits += missionCompletionCredits;
|
||||||
|
|
||||||
|
const credits = await addCredits(account, inventory, {
|
||||||
missionCompletionCredits,
|
missionCompletionCredits,
|
||||||
missionDropCredits: creditDrops ?? 0,
|
missionDropCredits: creditDrops ?? 0,
|
||||||
rngRewardCredits: inventoryChanges.RegularCredits ?? 0
|
rngRewardCredits: inventoryChanges.RegularCredits ?? 0
|
||||||
@ -1203,7 +1255,7 @@ export const addMissionRewards = async (
|
|||||||
|
|
||||||
if (rewardInfo.JobStage != undefined && rewardInfo.jobId) {
|
if (rewardInfo.JobStage != undefined && rewardInfo.jobId) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [jobType, unkIndex, hubNode, syndicateMissionId, locationTag] = rewardInfo.jobId.split("_");
|
const [jobType, unkIndex, hubNode, syndicateMissionId] = rewardInfo.jobId.split("_");
|
||||||
const syndicateMissions: ISyndicateMissionInfo[] = [];
|
const syndicateMissions: ISyndicateMissionInfo[] = [];
|
||||||
if (syndicateMissionId) {
|
if (syndicateMissionId) {
|
||||||
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId));
|
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId));
|
||||||
@ -1212,10 +1264,27 @@ export const addMissionRewards = async (
|
|||||||
if (syndicateEntry && syndicateEntry.Jobs) {
|
if (syndicateEntry && syndicateEntry.Jobs) {
|
||||||
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
|
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
|
||||||
if (syndicateEntry.Tag === "EntratiSyndicate") {
|
if (syndicateEntry.Tag === "EntratiSyndicate") {
|
||||||
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
|
if (
|
||||||
if (vault) currentJob = vault;
|
[
|
||||||
let medallionAmount = Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1));
|
"DeimosRuinsExterminateBounty",
|
||||||
|
"DeimosRuinsEscortBounty",
|
||||||
|
"DeimosRuinsMistBounty",
|
||||||
|
"DeimosRuinsPurifyBounty",
|
||||||
|
"DeimosRuinsSacBounty",
|
||||||
|
"VaultBounty"
|
||||||
|
].some(ending => jobType.endsWith(ending))
|
||||||
|
) {
|
||||||
|
const vault = syndicateEntry.Jobs.find(j => j.locationTag == rewardInfo.jobId!.split("_").at(-1));
|
||||||
|
if (vault) {
|
||||||
|
currentJob = vault;
|
||||||
|
if (jobType.endsWith("VaultBounty")) {
|
||||||
|
currentJob.xpAmounts = [currentJob.xpAmounts.reduce((partialSum, a) => partialSum + a, 0)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let medallionAmount = Math.floor(
|
||||||
|
Math.min(rewardInfo.JobStage, currentJob.xpAmounts.length - 1) / (rewardInfo.Q ? 0.8 : 1)
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
["DeimosEndlessAreaDefenseBounty", "DeimosEndlessExcavateBounty", "DeimosEndlessPurifyBounty"].some(
|
["DeimosEndlessAreaDefenseBounty", "DeimosEndlessExcavateBounty", "DeimosEndlessPurifyBounty"].some(
|
||||||
ending => jobType.endsWith(ending)
|
ending => jobType.endsWith(ending)
|
||||||
@ -1236,19 +1305,18 @@ export const addMissionRewards = async (
|
|||||||
SyndicateXPItemReward = medallionAmount;
|
SyndicateXPItemReward = medallionAmount;
|
||||||
} else {
|
} else {
|
||||||
if (rewardInfo.JobTier! >= 0) {
|
if (rewardInfo.JobTier! >= 0) {
|
||||||
AffiliationMods.push(
|
addStanding(
|
||||||
addStanding(
|
inventory,
|
||||||
inventory,
|
syndicateEntry.Tag,
|
||||||
syndicateEntry.Tag,
|
Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)),
|
||||||
Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1))
|
AffiliationMods
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (jobType.endsWith("Heists/HeistProfitTakerBountyOne") && rewardInfo.JobStage === 2) {
|
if (jobType.endsWith("Heists/HeistProfitTakerBountyOne") && rewardInfo.JobStage === 2) {
|
||||||
AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 1000));
|
addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods);
|
||||||
}
|
}
|
||||||
if (jobType.endsWith("Hunts/AllTeralystsHunt") && rewardInfo.JobStage === 2) {
|
if (jobType.endsWith("Hunts/AllTeralystsHunt") && rewardInfo.JobStage === 2) {
|
||||||
AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 5000));
|
addStanding(inventory, syndicateEntry.Tag, 5000, AffiliationMods);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
@ -1259,7 +1327,7 @@ export const addMissionRewards = async (
|
|||||||
"Heists/HeistExploiterBountyOne"
|
"Heists/HeistExploiterBountyOne"
|
||||||
].some(ending => jobType.endsWith(ending))
|
].some(ending => jobType.endsWith(ending))
|
||||||
) {
|
) {
|
||||||
AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 1000));
|
addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1267,9 +1335,9 @@ export const addMissionRewards = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rewardInfo.challengeMissionId) {
|
if (rewardInfo.challengeMissionId) {
|
||||||
const [syndicateTag, tierStr, chemistryStr] = rewardInfo.challengeMissionId.split("_");
|
const [syndicateTag, tierStr, chemistryBuddyStr] = rewardInfo.challengeMissionId.split("_");
|
||||||
const tier = Number(tierStr);
|
const tier = Number(tierStr);
|
||||||
const chemistry = Number(chemistryStr);
|
const chemistryBuddy = Number(chemistryBuddyStr);
|
||||||
const isSteelPath = missions?.Tier;
|
const isSteelPath = missions?.Tier;
|
||||||
if (syndicateTag === "ZarimanSyndicate") {
|
if (syndicateTag === "ZarimanSyndicate") {
|
||||||
let medallionAmount = tier + 1;
|
let medallionAmount = tier + 1;
|
||||||
@ -1284,24 +1352,21 @@ export const addMissionRewards = async (
|
|||||||
let standingAmount = (tier + 1) * 1000;
|
let standingAmount = (tier + 1) * 1000;
|
||||||
if (tier > 5) standingAmount = 7500; // InfestedLichBounty
|
if (tier > 5) standingAmount = 7500; // InfestedLichBounty
|
||||||
if (isSteelPath) standingAmount *= 1.5;
|
if (isSteelPath) standingAmount *= 1.5;
|
||||||
AffiliationMods.push(addStanding(inventory, syndicateTag, standingAmount));
|
addStanding(inventory, syndicateTag, standingAmount, AffiliationMods);
|
||||||
}
|
}
|
||||||
if (syndicateTag == "HexSyndicate" && chemistry && tier < 6) {
|
if (syndicateTag == "HexSyndicate" && tier < 6) {
|
||||||
const seed = getWorldState().SyndicateMissions.find(x => x.Tag == "HexSyndicate")!.Seed;
|
const buddy = chemistryBuddies[chemistryBuddy];
|
||||||
const { nodes, buddies } = getHexBounties(seed);
|
|
||||||
const buddy = buddies[tier];
|
|
||||||
logger.debug(`Hex seed is ${seed}, giving chemistry for ${buddy}`);
|
|
||||||
if (missions?.Tag != nodes[tier]) {
|
|
||||||
logger.warn(
|
|
||||||
`Uh-oh, tier ${tier} bounty should've been on ${nodes[tier]} but you were just on ${missions?.Tag}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const tomorrowAt0Utc = config.noKimCooldowns
|
|
||||||
? Date.now()
|
|
||||||
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
|
|
||||||
const dialogue = getDialogue(inventory, buddy);
|
const dialogue = getDialogue(inventory, buddy);
|
||||||
dialogue.Chemistry += chemistry;
|
if (Date.now() >= dialogue.BountyChemExpiry.getTime()) {
|
||||||
dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc);
|
logger.debug(`Giving 20 chemistry for ${buddy}`);
|
||||||
|
const tomorrowAt0Utc = config.noKimCooldowns
|
||||||
|
? Date.now()
|
||||||
|
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
|
||||||
|
dialogue.Chemistry += 20;
|
||||||
|
dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc);
|
||||||
|
} else {
|
||||||
|
logger.debug(`Already got today's chemistry for ${buddy}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isSteelPath) {
|
if (isSteelPath) {
|
||||||
await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1);
|
await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1);
|
||||||
@ -1322,48 +1387,61 @@ export const addMissionRewards = async (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
//creditBonus is not entirely accurate.
|
export const addCredits = async (
|
||||||
//TODO: consider ActiveBoosters
|
account: TAccountDocument,
|
||||||
export const addCredits = (
|
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
{
|
{
|
||||||
missionDropCredits,
|
missionDropCredits,
|
||||||
missionCompletionCredits,
|
missionCompletionCredits,
|
||||||
rngRewardCredits
|
rngRewardCredits
|
||||||
}: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number }
|
}: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number }
|
||||||
): IMissionCredits => {
|
): Promise<IMissionCredits> => {
|
||||||
const hasDailyCreditBonus = true;
|
|
||||||
const totalCredits = missionDropCredits + missionCompletionCredits + rngRewardCredits;
|
|
||||||
|
|
||||||
const finalCredits: IMissionCredits = {
|
const finalCredits: IMissionCredits = {
|
||||||
MissionCredits: [missionDropCredits, missionDropCredits],
|
MissionCredits: [missionDropCredits, missionDropCredits],
|
||||||
CreditBonus: [missionCompletionCredits, missionCompletionCredits],
|
CreditsBonus: [missionCompletionCredits, missionCompletionCredits],
|
||||||
TotalCredits: [totalCredits, totalCredits]
|
TotalCredits: [0, 0]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hasDailyCreditBonus) {
|
const today = Math.trunc(Date.now() / 86400000) * 86400;
|
||||||
|
if (account.DailyFirstWinDate != today) {
|
||||||
|
account.DailyFirstWinDate = today;
|
||||||
|
await account.save();
|
||||||
|
|
||||||
|
logger.debug(`daily first win, doubling missionCompletionCredits (${missionCompletionCredits})`);
|
||||||
|
|
||||||
|
finalCredits.DailyMissionBonus = true;
|
||||||
inventory.RegularCredits += missionCompletionCredits;
|
inventory.RegularCredits += missionCompletionCredits;
|
||||||
finalCredits.CreditBonus[1] *= 2;
|
finalCredits.CreditsBonus[1] *= 2;
|
||||||
finalCredits.MissionCredits[1] *= 2;
|
|
||||||
finalCredits.TotalCredits[1] *= 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasDailyCreditBonus) {
|
const totalCredits = finalCredits.MissionCredits[1] + finalCredits.CreditsBonus[1] + rngRewardCredits;
|
||||||
return finalCredits;
|
finalCredits.TotalCredits = [totalCredits, totalCredits];
|
||||||
|
|
||||||
|
if (config.worldState?.creditBoost) {
|
||||||
|
inventory.RegularCredits += finalCredits.TotalCredits[1];
|
||||||
|
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
|
||||||
}
|
}
|
||||||
return { ...finalCredits, DailyMissionBonus: true };
|
const now = Math.trunc(Date.now() / 1000); // TOVERIFY: Should we maybe subtract mission time as to apply credit boosters that expired during mission?
|
||||||
|
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBooster")?.ExpiryDate ?? 0) > now) {
|
||||||
|
inventory.RegularCredits += finalCredits.TotalCredits[1];
|
||||||
|
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
|
||||||
|
}
|
||||||
|
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) {
|
||||||
|
inventory.RegularCredits += finalCredits.TotalCredits[1];
|
||||||
|
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalCredits;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addFixedLevelRewards = (
|
export const addFixedLevelRewards = (
|
||||||
rewards: IMissionRewardExternal,
|
rewards: IMissionRewardExternal,
|
||||||
inventory: TInventoryDatabaseDocument,
|
|
||||||
MissionRewards: IMissionReward[],
|
MissionRewards: IMissionReward[],
|
||||||
rewardInfo?: IRewardInfo
|
rewardInfo?: IRewardInfo
|
||||||
): number => {
|
): number => {
|
||||||
let missionBonusCredits = 0;
|
let missionBonusCredits = 0;
|
||||||
if (rewards.credits) {
|
if (rewards.credits) {
|
||||||
missionBonusCredits += rewards.credits;
|
missionBonusCredits += rewards.credits;
|
||||||
inventory.RegularCredits += rewards.credits;
|
|
||||||
}
|
}
|
||||||
if (rewards.items) {
|
if (rewards.items) {
|
||||||
for (const item of rewards.items) {
|
for (const item of rewards.items) {
|
||||||
@ -1376,7 +1454,7 @@ export const addFixedLevelRewards = (
|
|||||||
if (rewards.countedItems) {
|
if (rewards.countedItems) {
|
||||||
for (const item of rewards.countedItems) {
|
for (const item of rewards.countedItems) {
|
||||||
MissionRewards.push({
|
MissionRewards.push({
|
||||||
StoreItem: `/Lotus/StoreItems${item.ItemType.substring("Lotus/".length)}`,
|
StoreItem: toStoreItem(item.ItemType),
|
||||||
ItemCount: item.ItemCount
|
ItemCount: item.ItemCount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1537,6 +1615,27 @@ function getRandomMissionDrops(
|
|||||||
? "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoSteelPathRNGRewards"
|
? "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoSteelPathRNGRewards"
|
||||||
: "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoNormalRNGRewards"
|
: "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoNormalRNGRewards"
|
||||||
];
|
];
|
||||||
|
} else if (RewardInfo.T == 17) {
|
||||||
|
if (mission?.Tier == 1) {
|
||||||
|
logger.warn(`non-steel path duviri murmur tier used on steel path?!`);
|
||||||
|
}
|
||||||
|
/*if (operation eight claw is active) {
|
||||||
|
drops.push({
|
||||||
|
StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent",
|
||||||
|
ItemCount: 10
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
rewardManifests = ["/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalChestRewards"];
|
||||||
|
} else if (RewardInfo.T == 19) {
|
||||||
|
/*if (operation eight claw is active) {
|
||||||
|
drops.push({
|
||||||
|
StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent",
|
||||||
|
ItemCount: 15
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
rewardManifests = [
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards"
|
||||||
|
];
|
||||||
} else if (RewardInfo.T == 70) {
|
} else if (RewardInfo.T == 70) {
|
||||||
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
|
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
|
||||||
drops.push({
|
drops.push({
|
||||||
@ -1552,7 +1651,7 @@ function getRandomMissionDrops(
|
|||||||
if (RewardInfo.jobId) {
|
if (RewardInfo.jobId) {
|
||||||
if (RewardInfo.JobStage! >= 0) {
|
if (RewardInfo.JobStage! >= 0) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [jobType, unkIndex, hubNode, syndicateMissionId, locationTag] = RewardInfo.jobId.split("_");
|
const [jobType, unkIndex, hubNode, syndicateMissionId] = RewardInfo.jobId.split("_");
|
||||||
let isEndlessJob = false;
|
let isEndlessJob = false;
|
||||||
if (syndicateMissionId) {
|
if (syndicateMissionId) {
|
||||||
const syndicateMissions: ISyndicateMissionInfo[] = [];
|
const syndicateMissions: ISyndicateMissionInfo[] = [];
|
||||||
@ -1564,19 +1663,30 @@ function getRandomMissionDrops(
|
|||||||
let job = syndicateEntry.Jobs[RewardInfo.JobTier!];
|
let job = syndicateEntry.Jobs[RewardInfo.JobTier!];
|
||||||
|
|
||||||
if (syndicateEntry.Tag === "EntratiSyndicate") {
|
if (syndicateEntry.Tag === "EntratiSyndicate") {
|
||||||
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
|
if (
|
||||||
if (vault && locationTag) job = vault;
|
[
|
||||||
// if (
|
"DeimosRuinsExterminateBounty",
|
||||||
// [
|
"DeimosRuinsEscortBounty",
|
||||||
// "DeimosRuinsExterminateBounty",
|
"DeimosRuinsMistBounty",
|
||||||
// "DeimosRuinsEscortBounty",
|
"DeimosRuinsPurifyBounty",
|
||||||
// "DeimosRuinsMistBounty",
|
"DeimosRuinsSacBounty",
|
||||||
// "DeimosRuinsPurifyBounty",
|
"VaultBounty"
|
||||||
// "DeimosRuinsSacBounty"
|
].some(ending => jobType.endsWith(ending))
|
||||||
// ].some(ending => jobType.endsWith(ending))
|
) {
|
||||||
// ) {
|
const vault = syndicateEntry.Jobs.find(
|
||||||
// job.rewards = "TODO"; // Droptable for Arcana Isolation Vault
|
j => j.locationTag === RewardInfo.jobId!.split("_").at(-1)
|
||||||
// }
|
);
|
||||||
|
if (vault) {
|
||||||
|
job = vault;
|
||||||
|
if (jobType.endsWith("VaultBounty")) {
|
||||||
|
job.rewards = job.rewards.replace(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/",
|
||||||
|
"/Supplementals/"
|
||||||
|
);
|
||||||
|
job.xpAmounts = [job.xpAmounts.reduce((partialSum, a) => partialSum + a, 0)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
"DeimosEndlessAreaDefenseBounty",
|
"DeimosEndlessAreaDefenseBounty",
|
||||||
@ -1635,20 +1745,28 @@ function getRandomMissionDrops(
|
|||||||
}
|
}
|
||||||
rewardManifests = [job.rewards];
|
rewardManifests = [job.rewards];
|
||||||
if (job.xpAmounts.length > 1) {
|
if (job.xpAmounts.length > 1) {
|
||||||
rotations = [RewardInfo.JobStage! % (job.xpAmounts.length - 1)];
|
const curentStage = RewardInfo.JobStage! + 1;
|
||||||
|
const totalStage = job.xpAmounts.length;
|
||||||
|
let tableIndex = 1; // Stage 2, Stage 3 of 4, and Stage 3 of 5
|
||||||
|
|
||||||
|
if (curentStage == 1) {
|
||||||
|
tableIndex = 0;
|
||||||
|
} else if (curentStage == totalStage) {
|
||||||
|
tableIndex = 3;
|
||||||
|
} else if (totalStage == 5 && curentStage == 4) {
|
||||||
|
tableIndex = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotations = [tableIndex];
|
||||||
} else {
|
} else {
|
||||||
rotations = [0];
|
rotations = [0];
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
RewardInfo.Q &&
|
RewardInfo.Q &&
|
||||||
(RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) &&
|
(RewardInfo.JobStage === job.xpAmounts.length - 1 || jobType.endsWith("VaultBounty")) &&
|
||||||
!isEndlessJob
|
!isEndlessJob
|
||||||
) {
|
) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
rotations.push(ExportRewards[job.rewards].length - 1);
|
||||||
if (ExportRewards[job.rewards]) {
|
|
||||||
rewardManifests.push(job.rewards);
|
|
||||||
rotations.push(ExportRewards[job.rewards].length - 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1813,6 +1931,23 @@ function getRandomMissionDrops(
|
|||||||
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
|
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.missionsCanGiveAllRelics) {
|
||||||
|
for (const drop of drops) {
|
||||||
|
const itemType = fromStoreItem(drop.StoreItem);
|
||||||
|
if (itemType in ExportRelics) {
|
||||||
|
const relic = ExportRelics[itemType];
|
||||||
|
const replacement = getRandomElement(
|
||||||
|
Object.entries(ExportRelics).filter(
|
||||||
|
arr => arr[1].era == relic.era && arr[1].quality == relic.quality
|
||||||
|
)
|
||||||
|
)!;
|
||||||
|
logger.debug(`replacing ${relic.era} ${relic.category} with ${replacement[1].category}`);
|
||||||
|
drop.StoreItem = toStoreItem(replacement[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return drops;
|
return drops;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1865,7 +2000,16 @@ const libraryPersonalTargetToAvatar: Record<string, string> = {
|
|||||||
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar"
|
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar"
|
||||||
};
|
};
|
||||||
|
|
||||||
const node_excluded_buddies: Record<string, string> = {
|
const chemistryBuddies: readonly string[] = [
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
|
||||||
|
"/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue"
|
||||||
|
];
|
||||||
|
|
||||||
|
/*const node_excluded_buddies: Record<string, string> = {
|
||||||
SolNode856: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
|
SolNode856: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
|
||||||
SolNode852: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
|
SolNode852: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
|
||||||
SolNode851: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
|
SolNode851: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
|
||||||
@ -1915,4 +2059,25 @@ const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { nodes, buddies };
|
return { nodes, buddies };
|
||||||
|
};*/
|
||||||
|
|
||||||
|
const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string; icon: string }> = {
|
||||||
|
"/Lotus/Types/Keys/GalleonRobberyAlert": {
|
||||||
|
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
|
||||||
|
msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgA",
|
||||||
|
sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleA",
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
|
||||||
|
},
|
||||||
|
"/Lotus/Types/Keys/GalleonRobberyAlertB": {
|
||||||
|
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
|
||||||
|
msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgB",
|
||||||
|
sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleB",
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
|
||||||
|
},
|
||||||
|
"/Lotus/Types/Keys/GalleonRobberyAlertC": {
|
||||||
|
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
|
||||||
|
msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgC",
|
||||||
|
sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleC",
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -8,12 +8,20 @@ import {
|
|||||||
updateCurrency,
|
updateCurrency,
|
||||||
updateSlots
|
updateSlots
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { getRandomWeightedRewardUc } from "@/src/services/rngService";
|
import { getRandomReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||||
import { applyStandingToVendorManifest, getVendorManifestByOid } from "@/src/services/serversideVendorsService";
|
import { applyStandingToVendorManifest, getVendorManifestByOid } from "@/src/services/serversideVendorsService";
|
||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IPurchaseRequest, IPurchaseResponse, SlotPurchase, IInventoryChanges } from "@/src/types/purchaseTypes";
|
import {
|
||||||
|
IPurchaseRequest,
|
||||||
|
IPurchaseResponse,
|
||||||
|
SlotPurchase,
|
||||||
|
IInventoryChanges,
|
||||||
|
PurchaseSource,
|
||||||
|
IPurchaseParams
|
||||||
|
} from "@/src/types/purchaseTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import worldState from "@/static/fixed_responses/worldState/worldState.json";
|
import { getWorldState } from "./worldStateService";
|
||||||
|
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
|
||||||
import {
|
import {
|
||||||
ExportBoosterPacks,
|
ExportBoosterPacks,
|
||||||
ExportBoosters,
|
ExportBoosters,
|
||||||
@ -28,6 +36,8 @@ import {
|
|||||||
import { config } from "./configService";
|
import { config } from "./configService";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
||||||
import { fromStoreItem, toStoreItem } from "./itemDataService";
|
import { fromStoreItem, toStoreItem } from "./itemDataService";
|
||||||
|
import { DailyDeal } from "../models/worldStateModel";
|
||||||
|
import { fromMongoDate, toMongoDate } from "../helpers/inventoryHelpers";
|
||||||
|
|
||||||
export const getStoreItemCategory = (storeItem: string): string => {
|
export const getStoreItemCategory = (storeItem: string): string => {
|
||||||
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
|
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
|
||||||
@ -44,6 +54,58 @@ export const getStoreItemTypesCategory = (typesItem: string): string => {
|
|||||||
return typeElements[1];
|
return typeElements[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tallyVendorPurchase = (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
inventoryChanges: IInventoryChanges,
|
||||||
|
VendorType: string,
|
||||||
|
ItemId: string,
|
||||||
|
numPurchased: number,
|
||||||
|
Expiry: Date
|
||||||
|
): void => {
|
||||||
|
if (!config.noVendorPurchaseLimits) {
|
||||||
|
inventory.RecentVendorPurchases ??= [];
|
||||||
|
let vendorPurchases = inventory.RecentVendorPurchases.find(x => x.VendorType == VendorType);
|
||||||
|
if (!vendorPurchases) {
|
||||||
|
vendorPurchases =
|
||||||
|
inventory.RecentVendorPurchases[
|
||||||
|
inventory.RecentVendorPurchases.push({
|
||||||
|
VendorType: VendorType,
|
||||||
|
PurchaseHistory: []
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
let historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId);
|
||||||
|
if (historyEntry) {
|
||||||
|
if (Date.now() >= historyEntry.Expiry.getTime()) {
|
||||||
|
historyEntry.NumPurchased = numPurchased;
|
||||||
|
historyEntry.Expiry = Expiry;
|
||||||
|
} else {
|
||||||
|
historyEntry.NumPurchased += numPurchased;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
historyEntry =
|
||||||
|
vendorPurchases.PurchaseHistory[
|
||||||
|
vendorPurchases.PurchaseHistory.push({
|
||||||
|
ItemId: ItemId,
|
||||||
|
NumPurchased: numPurchased,
|
||||||
|
Expiry: Expiry
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
inventoryChanges.NewVendorPurchase = {
|
||||||
|
VendorType: VendorType,
|
||||||
|
PurchaseHistory: [
|
||||||
|
{
|
||||||
|
ItemId: ItemId,
|
||||||
|
NumPurchased: historyEntry.NumPurchased,
|
||||||
|
Expiry: toMongoDate(Expiry)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
inventoryChanges.RecentVendorPurchases = inventoryChanges.NewVendorPurchase;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const handlePurchase = async (
|
export const handlePurchase = async (
|
||||||
purchaseRequest: IPurchaseRequest,
|
purchaseRequest: IPurchaseRequest,
|
||||||
inventory: TInventoryDatabaseDocument
|
inventory: TInventoryDatabaseDocument
|
||||||
@ -52,7 +114,7 @@ export const handlePurchase = async (
|
|||||||
|
|
||||||
const prePurchaseInventoryChanges: IInventoryChanges = {};
|
const prePurchaseInventoryChanges: IInventoryChanges = {};
|
||||||
let seed: bigint | undefined;
|
let seed: bigint | undefined;
|
||||||
if (purchaseRequest.PurchaseParams.Source == 7) {
|
if (purchaseRequest.PurchaseParams.Source == PurchaseSource.Vendor) {
|
||||||
let manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!);
|
let manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!);
|
||||||
if (manifest) {
|
if (manifest) {
|
||||||
manifest = applyStandingToVendorManifest(inventory, manifest);
|
manifest = applyStandingToVendorManifest(inventory, manifest);
|
||||||
@ -67,43 +129,30 @@ export const handlePurchase = async (
|
|||||||
if (!offer) {
|
if (!offer) {
|
||||||
throw new Error(`unknown vendor offer: ${ItemId ? ItemId : purchaseRequest.PurchaseParams.StoreItem}`);
|
throw new Error(`unknown vendor offer: ${ItemId ? ItemId : purchaseRequest.PurchaseParams.StoreItem}`);
|
||||||
}
|
}
|
||||||
if (offer.RegularPrice) {
|
if (!config.dontSubtractPurchaseCreditCost) {
|
||||||
combineInventoryChanges(
|
if (offer.RegularPrice) {
|
||||||
prePurchaseInventoryChanges,
|
updateCurrency(inventory, offer.RegularPrice[0], false, prePurchaseInventoryChanges);
|
||||||
updateCurrency(inventory, offer.RegularPrice[0], false)
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (offer.PremiumPrice) {
|
if (!config.dontSubtractPurchasePlatinumCost) {
|
||||||
combineInventoryChanges(
|
if (offer.PremiumPrice) {
|
||||||
prePurchaseInventoryChanges,
|
updateCurrency(inventory, offer.PremiumPrice[0], true, prePurchaseInventoryChanges);
|
||||||
updateCurrency(inventory, offer.PremiumPrice[0], true)
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (offer.ItemPrices) {
|
if (!config.dontSubtractPurchaseItemCost) {
|
||||||
handleItemPrices(
|
if (offer.ItemPrices) {
|
||||||
inventory,
|
handleItemPrices(
|
||||||
offer.ItemPrices,
|
inventory,
|
||||||
purchaseRequest.PurchaseParams.Quantity,
|
offer.ItemPrices,
|
||||||
prePurchaseInventoryChanges
|
purchaseRequest.PurchaseParams.Quantity,
|
||||||
);
|
prePurchaseInventoryChanges
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (offer.LocTagRandSeed !== undefined) {
|
if (offer.LocTagRandSeed !== undefined) {
|
||||||
seed = BigInt(offer.LocTagRandSeed);
|
seed = BigInt(offer.LocTagRandSeed);
|
||||||
}
|
}
|
||||||
if (!config.noVendorPurchaseLimits && ItemId) {
|
if (ItemId) {
|
||||||
inventory.RecentVendorPurchases ??= [];
|
|
||||||
let vendorPurchases = inventory.RecentVendorPurchases.find(
|
|
||||||
x => x.VendorType == manifest!.VendorInfo.TypeName
|
|
||||||
);
|
|
||||||
if (!vendorPurchases) {
|
|
||||||
vendorPurchases =
|
|
||||||
inventory.RecentVendorPurchases[
|
|
||||||
inventory.RecentVendorPurchases.push({
|
|
||||||
VendorType: manifest.VendorInfo.TypeName,
|
|
||||||
PurchaseHistory: []
|
|
||||||
}) - 1
|
|
||||||
];
|
|
||||||
}
|
|
||||||
let expiry = parseInt(offer.Expiry.$date.$numberLong);
|
let expiry = parseInt(offer.Expiry.$date.$numberLong);
|
||||||
if (purchaseRequest.PurchaseParams.IsWeekly) {
|
if (purchaseRequest.PurchaseParams.IsWeekly) {
|
||||||
const EPOCH = 1734307200 * 1000; // Monday
|
const EPOCH = 1734307200 * 1000; // Monday
|
||||||
@ -111,34 +160,14 @@ export const handlePurchase = async (
|
|||||||
const weekStart = EPOCH + week * 604800000;
|
const weekStart = EPOCH + week * 604800000;
|
||||||
expiry = weekStart + 604800000;
|
expiry = weekStart + 604800000;
|
||||||
}
|
}
|
||||||
const historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId);
|
tallyVendorPurchase(
|
||||||
let numPurchased = purchaseRequest.PurchaseParams.Quantity;
|
inventory,
|
||||||
if (historyEntry) {
|
prePurchaseInventoryChanges,
|
||||||
if (Date.now() >= historyEntry.Expiry.getTime()) {
|
manifest.VendorInfo.TypeName,
|
||||||
historyEntry.NumPurchased = numPurchased;
|
ItemId,
|
||||||
historyEntry.Expiry = new Date(expiry);
|
purchaseRequest.PurchaseParams.Quantity,
|
||||||
} else {
|
new Date(expiry)
|
||||||
numPurchased += historyEntry.NumPurchased;
|
);
|
||||||
historyEntry.NumPurchased += purchaseRequest.PurchaseParams.Quantity;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vendorPurchases.PurchaseHistory.push({
|
|
||||||
ItemId: ItemId,
|
|
||||||
NumPurchased: purchaseRequest.PurchaseParams.Quantity,
|
|
||||||
Expiry: new Date(expiry)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
prePurchaseInventoryChanges.NewVendorPurchase = {
|
|
||||||
VendorType: manifest.VendorInfo.TypeName,
|
|
||||||
PurchaseHistory: [
|
|
||||||
{
|
|
||||||
ItemId: ItemId,
|
|
||||||
NumPurchased: numPurchased,
|
|
||||||
Expiry: { $date: { $numberLong: expiry.toString() } }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
prePurchaseInventoryChanges.RecentVendorPurchases = prePurchaseInventoryChanges.NewVendorPurchase;
|
|
||||||
}
|
}
|
||||||
purchaseRequest.PurchaseParams.Quantity *= offer.QuantityMultiplier;
|
purchaseRequest.PurchaseParams.Quantity *= offer.QuantityMultiplier;
|
||||||
} else {
|
} else {
|
||||||
@ -160,18 +189,16 @@ export const handlePurchase = async (
|
|||||||
);
|
);
|
||||||
combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges);
|
combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges);
|
||||||
|
|
||||||
const currencyChanges = updateCurrency(
|
updateCurrency(
|
||||||
inventory,
|
inventory,
|
||||||
purchaseRequest.PurchaseParams.ExpectedPrice,
|
purchaseRequest.PurchaseParams.ExpectedPrice,
|
||||||
purchaseRequest.PurchaseParams.UsePremium
|
purchaseRequest.PurchaseParams.UsePremium,
|
||||||
|
prePurchaseInventoryChanges
|
||||||
);
|
);
|
||||||
purchaseResponse.InventoryChanges = {
|
|
||||||
...currencyChanges,
|
|
||||||
...purchaseResponse.InventoryChanges
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (purchaseRequest.PurchaseParams.Source) {
|
switch (purchaseRequest.PurchaseParams.Source) {
|
||||||
case 1: {
|
case PurchaseSource.VoidTrader: {
|
||||||
|
const worldState = getWorldState();
|
||||||
if (purchaseRequest.PurchaseParams.SourceId! != worldState.VoidTraders[0]._id.$oid) {
|
if (purchaseRequest.PurchaseParams.SourceId! != worldState.VoidTraders[0]._id.$oid) {
|
||||||
throw new Error("invalid request source");
|
throw new Error("invalid request source");
|
||||||
}
|
}
|
||||||
@ -179,25 +206,37 @@ export const handlePurchase = async (
|
|||||||
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
||||||
);
|
);
|
||||||
if (offer) {
|
if (offer) {
|
||||||
combineInventoryChanges(
|
if (!config.dontSubtractPurchaseCreditCost) {
|
||||||
purchaseResponse.InventoryChanges,
|
updateCurrency(inventory, offer.RegularPrice, false, purchaseResponse.InventoryChanges);
|
||||||
updateCurrency(inventory, offer.RegularPrice, false)
|
}
|
||||||
);
|
|
||||||
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
|
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
|
||||||
throw new Error(`vendor purchase should not have an expected price`);
|
throw new Error(`vendor purchase should not have an expected price`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const invItem: IMiscItem = {
|
if (offer.PrimePrice && !config.dontSubtractPurchaseItemCost) {
|
||||||
ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
|
const invItem: IMiscItem = {
|
||||||
ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1
|
ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
|
||||||
};
|
ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1
|
||||||
addMiscItems(inventory, [invItem]);
|
};
|
||||||
purchaseResponse.InventoryChanges.MiscItems ??= [];
|
addMiscItems(inventory, [invItem]);
|
||||||
purchaseResponse.InventoryChanges.MiscItems.push(invItem);
|
purchaseResponse.InventoryChanges.MiscItems ??= [];
|
||||||
|
purchaseResponse.InventoryChanges.MiscItems.push(invItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offer.Limit) {
|
||||||
|
tallyVendorPurchase(
|
||||||
|
inventory,
|
||||||
|
purchaseResponse.InventoryChanges,
|
||||||
|
"VoidTrader",
|
||||||
|
offer.ItemType,
|
||||||
|
purchaseRequest.PurchaseParams.Quantity,
|
||||||
|
fromMongoDate(worldState.VoidTraders[0].Expiry)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2:
|
case PurchaseSource.SyndicateFavor:
|
||||||
{
|
{
|
||||||
const syndicateTag = purchaseRequest.PurchaseParams.SyndicateTag!;
|
const syndicateTag = purchaseRequest.PurchaseParams.SyndicateTag!;
|
||||||
if (purchaseRequest.PurchaseParams.UseFreeFavor!) {
|
if (purchaseRequest.PurchaseParams.UseFreeFavor!) {
|
||||||
@ -211,7 +250,7 @@ export const handlePurchase = async (
|
|||||||
Title: lastTitle
|
Title: lastTitle
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
} else {
|
} else if (!config.dontSubtractPurchaseStandingCost) {
|
||||||
const syndicate = ExportSyndicates[syndicateTag];
|
const syndicate = ExportSyndicates[syndicateTag];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (syndicate) {
|
if (syndicate) {
|
||||||
@ -234,24 +273,24 @@ export const handlePurchase = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 7:
|
case PurchaseSource.DailyDeal:
|
||||||
|
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
|
||||||
|
throw new Error(`daily deal purchase should not have an expected price`);
|
||||||
|
}
|
||||||
|
await handleDailyDealPurchase(inventory, purchaseRequest.PurchaseParams, purchaseResponse);
|
||||||
|
break;
|
||||||
|
case PurchaseSource.Vendor:
|
||||||
if (purchaseRequest.PurchaseParams.SourceId! in ExportVendors) {
|
if (purchaseRequest.PurchaseParams.SourceId! in ExportVendors) {
|
||||||
const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!];
|
const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!];
|
||||||
const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem);
|
const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem);
|
||||||
if (offer) {
|
if (offer) {
|
||||||
if (typeof offer.credits == "number") {
|
if (typeof offer.credits == "number" && !config.dontSubtractPurchaseCreditCost) {
|
||||||
combineInventoryChanges(
|
updateCurrency(inventory, offer.credits, false, purchaseResponse.InventoryChanges);
|
||||||
purchaseResponse.InventoryChanges,
|
|
||||||
updateCurrency(inventory, offer.credits, false)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (typeof offer.platinum == "number") {
|
if (typeof offer.platinum == "number" && !config.dontSubtractPurchasePlatinumCost) {
|
||||||
combineInventoryChanges(
|
updateCurrency(inventory, offer.platinum, true, purchaseResponse.InventoryChanges);
|
||||||
purchaseResponse.InventoryChanges,
|
|
||||||
updateCurrency(inventory, offer.platinum, true)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (offer.itemPrices) {
|
if (offer.itemPrices && !config.dontSubtractPurchaseItemCost) {
|
||||||
handleItemPrices(
|
handleItemPrices(
|
||||||
inventory,
|
inventory,
|
||||||
offer.itemPrices,
|
offer.itemPrices,
|
||||||
@ -265,28 +304,30 @@ export const handlePurchase = async (
|
|||||||
throw new Error(`vendor purchase should not have an expected price`);
|
throw new Error(`vendor purchase should not have an expected price`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 18: {
|
case PurchaseSource.PrimeVaultTrader: {
|
||||||
if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) {
|
if (purchaseRequest.PurchaseParams.SourceId! != staticWorldState.PrimeVaultTraders[0]._id.$oid) {
|
||||||
throw new Error("invalid request source");
|
throw new Error("invalid request source");
|
||||||
}
|
}
|
||||||
const offer =
|
const offer =
|
||||||
worldState.PrimeVaultTraders[0].Manifest.find(
|
staticWorldState.PrimeVaultTraders[0].Manifest.find(
|
||||||
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
||||||
) ??
|
) ??
|
||||||
worldState.PrimeVaultTraders[0].EvergreenManifest.find(
|
staticWorldState.PrimeVaultTraders[0].EvergreenManifest.find(
|
||||||
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
|
||||||
);
|
);
|
||||||
if (offer) {
|
if (offer) {
|
||||||
if (offer.RegularPrice) {
|
if (offer.RegularPrice) {
|
||||||
const invItem: IMiscItem = {
|
if (!config.dontSubtractPurchaseItemCost) {
|
||||||
ItemType: "/Lotus/Types/Items/MiscItems/SchismKey",
|
const invItem: IMiscItem = {
|
||||||
ItemCount: offer.RegularPrice * purchaseRequest.PurchaseParams.Quantity * -1
|
ItemType: "/Lotus/Types/Items/MiscItems/SchismKey",
|
||||||
};
|
ItemCount: offer.RegularPrice * purchaseRequest.PurchaseParams.Quantity * -1
|
||||||
|
};
|
||||||
|
|
||||||
addMiscItems(inventory, [invItem]);
|
addMiscItems(inventory, [invItem]);
|
||||||
|
|
||||||
purchaseResponse.InventoryChanges.MiscItems ??= [];
|
purchaseResponse.InventoryChanges.MiscItems ??= [];
|
||||||
purchaseResponse.InventoryChanges.MiscItems.push(invItem);
|
purchaseResponse.InventoryChanges.MiscItems.push(invItem);
|
||||||
|
}
|
||||||
} else if (!config.infiniteRegalAya) {
|
} else if (!config.infiniteRegalAya) {
|
||||||
inventory.PrimeTokens -= offer.PrimePrice! * purchaseRequest.PurchaseParams.Quantity;
|
inventory.PrimeTokens -= offer.PrimePrice! * purchaseRequest.PurchaseParams.Quantity;
|
||||||
|
|
||||||
@ -326,6 +367,25 @@ const handleItemPrices = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const handleDailyDealPurchase = async (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
purchaseParams: IPurchaseParams,
|
||||||
|
purchaseResponse: IPurchaseResponse
|
||||||
|
): Promise<void> => {
|
||||||
|
const dailyDeal = (await DailyDeal.findOne({ StoreItem: purchaseParams.StoreItem }))!;
|
||||||
|
dailyDeal.AmountSold += 1;
|
||||||
|
await dailyDeal.save();
|
||||||
|
|
||||||
|
if (!config.dontSubtractPurchasePlatinumCost) {
|
||||||
|
updateCurrency(inventory, dailyDeal.SalePrice, true, purchaseResponse.InventoryChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.noVendorPurchaseLimits) {
|
||||||
|
inventory.UsedDailyDeals.push(purchaseParams.StoreItem);
|
||||||
|
purchaseResponse.DailyDealUsed = purchaseParams.StoreItem;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const handleBundleAcqusition = async (
|
export const handleBundleAcqusition = async (
|
||||||
storeItemName: string,
|
storeItemName: string,
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
@ -369,18 +429,28 @@ export const handleStoreItemAcquisition = async (
|
|||||||
} else {
|
} else {
|
||||||
const storeCategory = getStoreItemCategory(storeItemName);
|
const storeCategory = getStoreItemCategory(storeItemName);
|
||||||
const internalName = fromStoreItem(storeItemName);
|
const internalName = fromStoreItem(storeItemName);
|
||||||
logger.debug(`store category ${storeCategory}`);
|
|
||||||
if (!ignorePurchaseQuantity) {
|
if (!ignorePurchaseQuantity) {
|
||||||
if (internalName in ExportGear) {
|
if (internalName in ExportGear) {
|
||||||
quantity *= ExportGear[internalName].purchaseQuantity || 1;
|
quantity *= ExportGear[internalName].purchaseQuantity || 1;
|
||||||
|
logger.debug(`factored quantity is ${quantity}`);
|
||||||
} else if (internalName in ExportResources) {
|
} else if (internalName in ExportResources) {
|
||||||
quantity *= ExportResources[internalName].purchaseQuantity || 1;
|
quantity *= ExportResources[internalName].purchaseQuantity || 1;
|
||||||
|
logger.debug(`factored quantity is ${quantity}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logger.debug(`store category ${storeCategory}`);
|
||||||
switch (storeCategory) {
|
switch (storeCategory) {
|
||||||
default: {
|
default: {
|
||||||
purchaseResponse = {
|
purchaseResponse = {
|
||||||
InventoryChanges: await addItem(inventory, internalName, quantity, premiumPurchase, seed)
|
InventoryChanges: await addItem(
|
||||||
|
inventory,
|
||||||
|
internalName,
|
||||||
|
quantity,
|
||||||
|
premiumPurchase,
|
||||||
|
seed,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
)
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -470,12 +540,57 @@ const handleBoosterPackPurchase = async (
|
|||||||
"attempt to roll over 100 booster packs in a single go. possible but unlikely to be desirable for the user or the server."
|
"attempt to roll over 100 booster packs in a single go. possible but unlikely to be desirable for the user or the server."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const specialItemReward = pack.components.find(x => x.PityIncreaseRate);
|
||||||
for (let i = 0; i != quantity; ++i) {
|
for (let i = 0; i != quantity; ++i) {
|
||||||
const disallowedItems = new Set();
|
if (specialItemReward) {
|
||||||
for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) {
|
{
|
||||||
const weights = pack.rarityWeightsPerRoll[roll];
|
const normalComponents = [];
|
||||||
const result = getRandomWeightedRewardUc(pack.components, weights);
|
for (const comp of pack.components) {
|
||||||
if (result) {
|
if (!comp.PityIncreaseRate) {
|
||||||
|
const { Probability, ...rest } = comp;
|
||||||
|
normalComponents.push({
|
||||||
|
...rest,
|
||||||
|
probability: Probability!
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result = getRandomReward(normalComponents)!;
|
||||||
|
logger.debug(`booster pack rolled`, result);
|
||||||
|
purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};';
|
||||||
|
combineInventoryChanges(
|
||||||
|
purchaseResponse.InventoryChanges,
|
||||||
|
await addItem(inventory, result.Item, result.Amount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inventory.WeaponSkins.some(x => x.ItemType == specialItemReward.Item)) {
|
||||||
|
inventory.SpecialItemRewardAttenuation ??= [];
|
||||||
|
let atten = inventory.SpecialItemRewardAttenuation.find(x => x.Tag == specialItemReward.Item);
|
||||||
|
if (!atten) {
|
||||||
|
atten =
|
||||||
|
inventory.SpecialItemRewardAttenuation[
|
||||||
|
inventory.SpecialItemRewardAttenuation.push({
|
||||||
|
Tag: specialItemReward.Item,
|
||||||
|
Atten: specialItemReward.Probability!
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (Math.random() < atten.Atten) {
|
||||||
|
purchaseResponse.BoosterPackItems += toStoreItem(specialItemReward.Item) + ',{"lvl":0};';
|
||||||
|
combineInventoryChanges(
|
||||||
|
purchaseResponse.InventoryChanges,
|
||||||
|
await addItem(inventory, specialItemReward.Item)
|
||||||
|
);
|
||||||
|
// TOVERIFY: Is the SpecialItemRewardAttenuation entry removed now?
|
||||||
|
} else {
|
||||||
|
atten.Atten += specialItemReward.PityIncreaseRate!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const disallowedItems = new Set();
|
||||||
|
for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) {
|
||||||
|
const weights = pack.rarityWeightsPerRoll[roll];
|
||||||
|
const result = getRandomWeightedRewardUc(pack.components, weights)!;
|
||||||
logger.debug(`booster pack rolled`, result);
|
logger.debug(`booster pack rolled`, result);
|
||||||
if (disallowedItems.has(result.Item)) {
|
if (disallowedItems.has(result.Item)) {
|
||||||
logger.debug(`oops, can't use that one; trying again`);
|
logger.debug(`oops, can't use that one; trying again`);
|
||||||
@ -485,9 +600,12 @@ const handleBoosterPackPurchase = async (
|
|||||||
disallowedItems.add(result.Item);
|
disallowedItems.add(result.Item);
|
||||||
}
|
}
|
||||||
purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};';
|
purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};';
|
||||||
combineInventoryChanges(purchaseResponse.InventoryChanges, await addItem(inventory, result.Item, 1));
|
combineInventoryChanges(
|
||||||
|
purchaseResponse.InventoryChanges,
|
||||||
|
await addItem(inventory, result.Item, result.Amount)
|
||||||
|
);
|
||||||
|
++roll;
|
||||||
}
|
}
|
||||||
++roll;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return purchaseResponse;
|
return purchaseResponse;
|
||||||
@ -522,7 +640,9 @@ const handleTypesPurchase = async (
|
|||||||
logger.debug(`type category ${typeCategory}`);
|
logger.debug(`type category ${typeCategory}`);
|
||||||
switch (typeCategory) {
|
switch (typeCategory) {
|
||||||
default:
|
default:
|
||||||
return { InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed) };
|
return {
|
||||||
|
InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed, undefined, true)
|
||||||
|
};
|
||||||
case "BoosterPacks":
|
case "BoosterPacks":
|
||||||
return handleBoosterPackPurchase(typesName, inventory, quantity);
|
return handleBoosterPackPurchase(typesName, inventory, quantity);
|
||||||
case "SlotItems":
|
case "SlotItems":
|
||||||
|
@ -331,7 +331,7 @@ export const giveKeyChainMissionReward = async (
|
|||||||
const fixedLevelRewards = getLevelKeyRewards(missionName);
|
const fixedLevelRewards = getLevelKeyRewards(missionName);
|
||||||
if (fixedLevelRewards.levelKeyRewards) {
|
if (fixedLevelRewards.levelKeyRewards) {
|
||||||
const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
|
const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
|
||||||
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards);
|
inventory.RegularCredits += addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, missionRewards);
|
||||||
|
|
||||||
for (const reward of missionRewards) {
|
for (const reward of missionRewards) {
|
||||||
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
|
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
|
||||||
|
@ -107,6 +107,16 @@ export class SRng {
|
|||||||
return arr[this.randomInt(0, arr.length - 1)];
|
return arr[this.randomInt(0, arr.length - 1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
randomElementPop<T>(arr: T[]): T | undefined {
|
||||||
|
if (arr.length != 0) {
|
||||||
|
const index = this.randomInt(0, arr.length - 1);
|
||||||
|
const elm = arr[index];
|
||||||
|
arr.splice(index, 1);
|
||||||
|
return elm;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
randomFloat(): number {
|
randomFloat(): number {
|
||||||
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
|
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
|
||||||
return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645;
|
return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645;
|
||||||
|
@ -149,7 +149,8 @@ export const handleInventoryItemConfigChange = async (
|
|||||||
} else {
|
} else {
|
||||||
const inventoryItem = inventory.WeaponSkins.id(itemId);
|
const inventoryItem = inventory.WeaponSkins.id(itemId);
|
||||||
if (!inventoryItem) {
|
if (!inventoryItem) {
|
||||||
throw new Error(`inventory item WeaponSkins not found with id ${itemId}`);
|
logger.warn(`inventory item WeaponSkins not found with id ${itemId}`);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if ("Favorite" in itemConfigEntries) {
|
if ("Favorite" in itemConfigEntries) {
|
||||||
inventoryItem.Favorite = itemConfigEntries.Favorite;
|
inventoryItem.Favorite = itemConfigEntries.Favorite;
|
||||||
@ -166,8 +167,13 @@ export const handleInventoryItemConfigChange = async (
|
|||||||
inventory.LotusCustomization = equipmentChanges.LotusCustomization;
|
inventory.LotusCustomization = equipmentChanges.LotusCustomization;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "ValidNewLoadoutId": {
|
||||||
|
logger.debug(`ignoring ValidNewLoadoutId (${equipmentChanges.ValidNewLoadoutId})`);
|
||||||
|
// seems always equal to the id of loadout config NORMAL[0], likely has no purpose and we're free to ignore it
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
if (equipmentKeys.includes(equipmentName as TEquipmentKey) && equipmentName != "ValidNewLoadoutId") {
|
if (equipmentKeys.includes(equipmentName as TEquipmentKey)) {
|
||||||
logger.debug(`general Item config saved of type ${equipmentName}`, {
|
logger.debug(`general Item config saved of type ${equipmentName}`, {
|
||||||
config: equipment
|
config: equipment
|
||||||
});
|
});
|
||||||
@ -177,7 +183,8 @@ export const handleInventoryItemConfigChange = async (
|
|||||||
const inventoryItem = inventory[equipmentName].id(itemId);
|
const inventoryItem = inventory[equipmentName].id(itemId);
|
||||||
|
|
||||||
if (!inventoryItem) {
|
if (!inventoryItem) {
|
||||||
throw new Error(`inventory item ${equipmentName} not found with id ${itemId}`);
|
logger.warn(`inventory item ${equipmentName} not found with id ${itemId}`);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [configId, config] of Object.entries(itemConfigEntries)) {
|
for (const [configId, config] of Object.entries(itemConfigEntries)) {
|
||||||
@ -214,7 +221,7 @@ export const handleInventoryItemConfigChange = async (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`loadout category not implemented, changes may be lost: ${equipmentName}`, {
|
logger.error(`loadout category not implemented, changes will be lost: ${equipmentName}`, {
|
||||||
config: equipment
|
config: equipment
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,64 +1,12 @@
|
|||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { isDev } from "@/src/helpers/pathHelper";
|
import { args } from "@/src/helpers/commandLineArguments";
|
||||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { mixSeeds, SRng } from "@/src/services/rngService";
|
import { mixSeeds, SRng } from "@/src/services/rngService";
|
||||||
import { IMongoDate } from "@/src/types/commonTypes";
|
|
||||||
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
|
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ExportVendors, IRange, IVendor } from "warframe-public-export-plus";
|
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
|
||||||
|
import { config } from "./configService";
|
||||||
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
|
|
||||||
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
|
|
||||||
import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json";
|
|
||||||
import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json";
|
|
||||||
import DeimosHivemindCommisionsManifestProspector from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json";
|
|
||||||
import DeimosHivemindCommisionsManifestTokenVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json";
|
|
||||||
import DeimosHivemindCommisionsManifestWeaponsmith from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json";
|
|
||||||
import DeimosHivemindTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json";
|
|
||||||
import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json";
|
|
||||||
import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json";
|
|
||||||
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
|
|
||||||
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
|
|
||||||
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
|
|
||||||
import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json";
|
|
||||||
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
|
|
||||||
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
|
|
||||||
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
|
|
||||||
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
|
|
||||||
import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json";
|
|
||||||
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
|
|
||||||
import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json";
|
|
||||||
import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json";
|
|
||||||
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
|
|
||||||
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
|
|
||||||
|
|
||||||
const rawVendorManifests: IVendorManifest[] = [
|
|
||||||
ArchimedeanVendorManifest,
|
|
||||||
DeimosEntratiFragmentVendorProductsManifest,
|
|
||||||
DeimosHivemindCommisionsManifestFishmonger,
|
|
||||||
DeimosHivemindCommisionsManifestPetVendor,
|
|
||||||
DeimosHivemindCommisionsManifestProspector,
|
|
||||||
DeimosHivemindCommisionsManifestTokenVendor,
|
|
||||||
DeimosHivemindCommisionsManifestWeaponsmith,
|
|
||||||
DeimosHivemindTokenVendorManifest,
|
|
||||||
DeimosPetVendorManifest,
|
|
||||||
DeimosProspectorVendorManifest,
|
|
||||||
DuviriAcrithisVendorManifest,
|
|
||||||
EntratiLabsEntratiLabsCommisionsManifest,
|
|
||||||
EntratiLabsEntratiLabVendorManifest,
|
|
||||||
HubsIronwakeDondaVendorManifest, // uses preprocessing
|
|
||||||
HubsRailjackCrewMemberVendorManifest,
|
|
||||||
MaskSalesmanManifest,
|
|
||||||
Nova1999ConquestShopManifest,
|
|
||||||
OstronPetVendorManifest,
|
|
||||||
OstronProspectorVendorManifest,
|
|
||||||
SolarisDebtTokenVendorRepossessionsManifest,
|
|
||||||
SolarisProspectorVendorManifest,
|
|
||||||
Temple1999VendorManifest,
|
|
||||||
TeshinHardModeVendorManifest, // uses preprocessing
|
|
||||||
ZarimanCommisionsManifestArchimedean
|
|
||||||
];
|
|
||||||
|
|
||||||
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
|
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
|
||||||
cycleOffset?: number;
|
cycleOffset?: number;
|
||||||
@ -83,10 +31,6 @@ const generatableVendors: IGeneratableVendorInfo[] = [
|
|||||||
cycleOffset: 1744934400_000,
|
cycleOffset: 1744934400_000,
|
||||||
cycleDuration: 4 * unixTimesInMs.day
|
cycleDuration: 4 * unixTimesInMs.day
|
||||||
}
|
}
|
||||||
// {
|
|
||||||
// _id: { $oid: "5dbb4c41e966f7886c3ce939" },
|
|
||||||
// TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest"
|
|
||||||
// }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const getVendorOid = (typeName: string): string => {
|
const getVendorOid = (typeName: string): string => {
|
||||||
@ -101,60 +45,60 @@ const gcd = (a: number, b: number): number => {
|
|||||||
const getCycleDuration = (manifest: IVendor): number => {
|
const getCycleDuration = (manifest: IVendor): number => {
|
||||||
let dur = 0;
|
let dur = 0;
|
||||||
for (const item of manifest.items) {
|
for (const item of manifest.items) {
|
||||||
if (typeof item.durationHours != "number") {
|
if (item.alwaysOffered) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const durationHours = item.rotatedWeekly ? 168 : item.durationHours;
|
||||||
|
if (typeof durationHours != "number") {
|
||||||
dur = 1;
|
dur = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (dur != item.durationHours) {
|
if (dur != durationHours) {
|
||||||
dur = gcd(dur, item.durationHours);
|
dur = gcd(dur, durationHours);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dur * unixTimesInMs.hour;
|
return dur * unixTimesInMs.hour;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
|
export const getVendorManifestByTypeName = (typeName: string, fullStock?: boolean): IVendorManifest | undefined => {
|
||||||
for (const vendorManifest of rawVendorManifests) {
|
|
||||||
if (vendorManifest.VendorInfo.TypeName == typeName) {
|
|
||||||
return preprocessVendorManifest(vendorManifest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const vendorInfo of generatableVendors) {
|
for (const vendorInfo of generatableVendors) {
|
||||||
if (vendorInfo.TypeName == typeName) {
|
if (vendorInfo.TypeName == typeName) {
|
||||||
return generateVendorManifest(vendorInfo);
|
return generateVendorManifest(vendorInfo, fullStock ?? config.fullyStockedVendors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeName in ExportVendors) {
|
if (typeName in ExportVendors) {
|
||||||
const manifest = ExportVendors[typeName];
|
const manifest = ExportVendors[typeName];
|
||||||
return generateVendorManifest({
|
return generateVendorManifest(
|
||||||
_id: { $oid: getVendorOid(typeName) },
|
{
|
||||||
TypeName: typeName,
|
_id: { $oid: getVendorOid(typeName) },
|
||||||
RandomSeedType: manifest.randomSeedType,
|
TypeName: typeName,
|
||||||
cycleDuration: getCycleDuration(manifest)
|
RandomSeedType: manifest.randomSeedType,
|
||||||
});
|
cycleDuration: getCycleDuration(manifest)
|
||||||
|
},
|
||||||
|
fullStock ?? config.fullyStockedVendors
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
|
export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
|
||||||
for (const vendorManifest of rawVendorManifests) {
|
|
||||||
if (vendorManifest.VendorInfo._id.$oid == oid) {
|
|
||||||
return preprocessVendorManifest(vendorManifest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const vendorInfo of generatableVendors) {
|
for (const vendorInfo of generatableVendors) {
|
||||||
if (vendorInfo._id.$oid == oid) {
|
if (vendorInfo._id.$oid == oid) {
|
||||||
return generateVendorManifest(vendorInfo);
|
return generateVendorManifest(vendorInfo, config.fullyStockedVendors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [typeName, manifest] of Object.entries(ExportVendors)) {
|
for (const [typeName, manifest] of Object.entries(ExportVendors)) {
|
||||||
const typeNameOid = getVendorOid(typeName);
|
const typeNameOid = getVendorOid(typeName);
|
||||||
if (typeNameOid == oid) {
|
if (typeNameOid == oid) {
|
||||||
return generateVendorManifest({
|
return generateVendorManifest(
|
||||||
_id: { $oid: typeNameOid },
|
{
|
||||||
TypeName: typeName,
|
_id: { $oid: typeNameOid },
|
||||||
RandomSeedType: manifest.randomSeedType,
|
TypeName: typeName,
|
||||||
cycleDuration: getCycleDuration(manifest)
|
RandomSeedType: manifest.randomSeedType,
|
||||||
});
|
cycleDuration: getCycleDuration(manifest)
|
||||||
|
},
|
||||||
|
config.fullyStockedVendors
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -197,30 +141,6 @@ export const applyStandingToVendorManifest = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const preprocessVendorManifest = (originalManifest: IVendorManifest): IVendorManifest => {
|
|
||||||
if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) {
|
|
||||||
const manifest = structuredClone(originalManifest);
|
|
||||||
const info = manifest.VendorInfo;
|
|
||||||
refreshExpiry(info.Expiry);
|
|
||||||
for (const offer of info.ItemManifest) {
|
|
||||||
refreshExpiry(offer.Expiry);
|
|
||||||
}
|
|
||||||
return manifest;
|
|
||||||
}
|
|
||||||
return originalManifest;
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshExpiry = (expiry: IMongoDate): void => {
|
|
||||||
const period = parseInt(expiry.$date.$numberLong);
|
|
||||||
if (Date.now() >= period) {
|
|
||||||
const epoch = 1734307200_000; // Monday (for weekly schedules)
|
|
||||||
const iteration = Math.trunc((Date.now() - epoch) / period);
|
|
||||||
const start = epoch + iteration * period;
|
|
||||||
const end = start + period;
|
|
||||||
expiry.$date.$numberLong = end.toString();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toRange = (value: IRange | number): IRange => {
|
const toRange = (value: IRange | number): IRange => {
|
||||||
if (typeof value == "number") {
|
if (typeof value == "number") {
|
||||||
return { minValue: value, maxValue: value };
|
return { minValue: value, maxValue: value };
|
||||||
@ -228,9 +148,54 @@ const toRange = (value: IRange | number): IRange => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCycleDurationRange = (manifest: IVendor): IRange | undefined => {
|
||||||
|
const res: IRange = { minValue: Number.MAX_SAFE_INTEGER, maxValue: 0 };
|
||||||
|
for (const offer of manifest.items) {
|
||||||
|
if (offer.durationHours) {
|
||||||
|
const range = toRange(offer.durationHours);
|
||||||
|
if (res.minValue > range.minValue) {
|
||||||
|
res.minValue = range.minValue;
|
||||||
|
}
|
||||||
|
if (res.maxValue < range.maxValue) {
|
||||||
|
res.maxValue = range.maxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.maxValue != 0 ? res : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TOfferId = string;
|
||||||
|
|
||||||
|
const getOfferId = (offer: IVendorOffer | IItemManifest): TOfferId => {
|
||||||
|
if ("storeItem" in offer) {
|
||||||
|
// IVendorOffer
|
||||||
|
return offer.storeItem + "x" + offer.quantity;
|
||||||
|
} else {
|
||||||
|
// IItemManifest
|
||||||
|
return offer.StoreItem + "x" + offer.QuantityMultiplier;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let vendorManifestsUsingFullStock = false;
|
||||||
const vendorManifestCache: Record<string, IVendorManifest> = {};
|
const vendorManifestCache: Record<string, IVendorManifest> = {};
|
||||||
|
|
||||||
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
|
const clearVendorCache = (): void => {
|
||||||
|
for (const k of Object.keys(vendorManifestCache)) {
|
||||||
|
delete vendorManifestCache[k];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateVendorManifest = (
|
||||||
|
vendorInfo: IGeneratableVendorInfo,
|
||||||
|
fullStock: boolean | undefined
|
||||||
|
): IVendorManifest => {
|
||||||
|
fullStock ??= config.fullyStockedVendors;
|
||||||
|
fullStock ??= false;
|
||||||
|
if (vendorManifestsUsingFullStock != fullStock) {
|
||||||
|
vendorManifestsUsingFullStock = fullStock;
|
||||||
|
clearVendorCache();
|
||||||
|
}
|
||||||
|
|
||||||
if (!(vendorInfo.TypeName in vendorManifestCache)) {
|
if (!(vendorInfo.TypeName in vendorManifestCache)) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
|
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
|
||||||
@ -244,10 +209,16 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
}
|
}
|
||||||
const cacheEntry = vendorManifestCache[vendorInfo.TypeName];
|
const cacheEntry = vendorManifestCache[vendorInfo.TypeName];
|
||||||
const info = cacheEntry.VendorInfo;
|
const info = cacheEntry.VendorInfo;
|
||||||
if (Date.now() >= parseInt(info.Expiry.$date.$numberLong)) {
|
const manifest = ExportVendors[vendorInfo.TypeName];
|
||||||
|
const cycleDurationRange = getCycleDurationRange(manifest);
|
||||||
|
let now = Date.now();
|
||||||
|
if (cycleDurationRange && cycleDurationRange.minValue != cycleDurationRange.maxValue) {
|
||||||
|
now -= (cycleDurationRange.maxValue - 1) * unixTimesInMs.hour;
|
||||||
|
}
|
||||||
|
while (Date.now() >= parseInt(info.Expiry.$date.$numberLong)) {
|
||||||
// Remove expired offers
|
// Remove expired offers
|
||||||
for (let i = 0; i != info.ItemManifest.length; ) {
|
for (let i = 0; i != info.ItemManifest.length; ) {
|
||||||
if (Date.now() >= parseInt(info.ItemManifest[i].Expiry.$date.$numberLong)) {
|
if (now >= parseInt(info.ItemManifest[i].Expiry.$date.$numberLong)) {
|
||||||
info.ItemManifest.splice(i, 1);
|
info.ItemManifest.splice(i, 1);
|
||||||
} else {
|
} else {
|
||||||
++i;
|
++i;
|
||||||
@ -258,62 +229,121 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
const vendorSeed = parseInt(vendorInfo._id.$oid.substring(16), 16);
|
const vendorSeed = parseInt(vendorInfo._id.$oid.substring(16), 16);
|
||||||
const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000;
|
const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000;
|
||||||
const cycleDuration = vendorInfo.cycleDuration;
|
const cycleDuration = vendorInfo.cycleDuration;
|
||||||
const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration);
|
const cycleIndex = Math.trunc((now - cycleOffset) / cycleDuration);
|
||||||
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
|
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
|
||||||
const manifest = ExportVendors[vendorInfo.TypeName];
|
const offersToAdd: IVendorOffer[] = [];
|
||||||
const offersToAdd = [];
|
if (manifest.isOneBinPerCycle) {
|
||||||
if (
|
if (fullStock) {
|
||||||
manifest.numItems &&
|
for (const rawItem of manifest.items) {
|
||||||
(manifest.numItems.minValue != manifest.numItems.maxValue ||
|
offersToAdd.push(rawItem);
|
||||||
manifest.items.length != manifest.numItems.minValue) &&
|
}
|
||||||
!manifest.isOneBinPerCycle
|
} else {
|
||||||
) {
|
const binThisCycle = cycleIndex % 2; // Note: May want to check the actual number of bins, but this is only used for coda weapons right now.
|
||||||
const remainingItemCapacity: Record<string, number> = {};
|
for (const rawItem of manifest.items) {
|
||||||
for (const item of manifest.items) {
|
if (rawItem.bin == binThisCycle) {
|
||||||
remainingItemCapacity[item.storeItem] = 1 + item.duplicates;
|
offersToAdd.push(rawItem);
|
||||||
}
|
}
|
||||||
for (const offer of info.ItemManifest) {
|
|
||||||
remainingItemCapacity[offer.StoreItem] -= 1;
|
|
||||||
}
|
|
||||||
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
|
|
||||||
while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
|
|
||||||
// TODO: Consider per-bin item limits
|
|
||||||
// TODO: Consider item probability weightings
|
|
||||||
const item = rng.randomElement(manifest.items)!;
|
|
||||||
if (remainingItemCapacity[item.storeItem] != 0) {
|
|
||||||
remainingItemCapacity[item.storeItem] -= 1;
|
|
||||||
offersToAdd.push(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let binThisCycle;
|
// Compute vendor requirements, subtracting existing offers
|
||||||
if (manifest.isOneBinPerCycle) {
|
const remainingItemCapacity: Record<TOfferId, number> = {};
|
||||||
binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
|
const missingItemsPerBin: Record<number, number> = {};
|
||||||
|
let numOffersThatNeedToMatchABin = 0;
|
||||||
|
if (manifest.numItemsPerBin) {
|
||||||
|
for (let bin = 0; bin != manifest.numItemsPerBin.length; ++bin) {
|
||||||
|
missingItemsPerBin[bin] = manifest.numItemsPerBin[bin];
|
||||||
|
numOffersThatNeedToMatchABin += manifest.numItemsPerBin[bin];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const rawItem of manifest.items) {
|
for (const item of manifest.items) {
|
||||||
if (!manifest.isOneBinPerCycle || rawItem.bin == binThisCycle) {
|
remainingItemCapacity[getOfferId(item)] = 1 + item.duplicates;
|
||||||
offersToAdd.push(rawItem);
|
}
|
||||||
|
for (const offer of info.ItemManifest) {
|
||||||
|
remainingItemCapacity[getOfferId(offer)] -= 1;
|
||||||
|
const bin = parseInt(offer.Bin.substring(4));
|
||||||
|
if (missingItemsPerBin[bin]) {
|
||||||
|
missingItemsPerBin[bin] -= 1;
|
||||||
|
numOffersThatNeedToMatchABin -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For most vendors, the offers seem to roughly be in reverse order from the manifest. Coda weapons are an odd exception.
|
// Add permanent offers
|
||||||
if (!manifest.isOneBinPerCycle) {
|
let numUncountedOffers = 0;
|
||||||
offersToAdd.reverse();
|
let numCountedOffers = 0;
|
||||||
|
let offset = 0;
|
||||||
|
for (const item of manifest.items) {
|
||||||
|
if (item.alwaysOffered || item.rotatedWeekly) {
|
||||||
|
++numUncountedOffers;
|
||||||
|
const id = getOfferId(item);
|
||||||
|
if (remainingItemCapacity[id] != 0) {
|
||||||
|
remainingItemCapacity[id] -= 1;
|
||||||
|
offersToAdd.push(item);
|
||||||
|
++offset;
|
||||||
|
}
|
||||||
|
if (missingItemsPerBin[item.bin]) {
|
||||||
|
missingItemsPerBin[item.bin] -= 1;
|
||||||
|
numOffersThatNeedToMatchABin -= 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
numCountedOffers += 1 + item.duplicates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add counted offers
|
||||||
|
const useRng =
|
||||||
|
manifest.numItems &&
|
||||||
|
(manifest.numItems.minValue != manifest.numItems.maxValue ||
|
||||||
|
manifest.numItems.minValue != numCountedOffers);
|
||||||
|
const numItemsTarget = fullStock
|
||||||
|
? numUncountedOffers + numCountedOffers
|
||||||
|
: manifest.numItems
|
||||||
|
? numUncountedOffers +
|
||||||
|
(useRng
|
||||||
|
? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
|
||||||
|
: manifest.numItems.minValue)
|
||||||
|
: manifest.items.length;
|
||||||
|
let i = 0;
|
||||||
|
const rollableOffers = manifest.items.filter(x => x.probability !== undefined) as (Omit<
|
||||||
|
IVendorOffer,
|
||||||
|
"probability"
|
||||||
|
> & { probability: number })[];
|
||||||
|
while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
|
||||||
|
const item = useRng ? rng.randomReward(rollableOffers)! : rollableOffers[i++];
|
||||||
|
if (
|
||||||
|
remainingItemCapacity[getOfferId(item)] != 0 &&
|
||||||
|
(numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin])
|
||||||
|
) {
|
||||||
|
remainingItemCapacity[getOfferId(item)] -= 1;
|
||||||
|
if (missingItemsPerBin[item.bin]) {
|
||||||
|
missingItemsPerBin[item.bin] -= 1;
|
||||||
|
numOffersThatNeedToMatchABin -= 1;
|
||||||
|
}
|
||||||
|
offersToAdd.splice(offset, 0, item);
|
||||||
|
}
|
||||||
|
if (i == rollableOffers.length) {
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const cycleStart = cycleOffset + cycleIndex * cycleDuration;
|
const cycleStart = cycleOffset + cycleIndex * cycleDuration;
|
||||||
for (const rawItem of offersToAdd) {
|
for (const rawItem of offersToAdd) {
|
||||||
const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration);
|
const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration);
|
||||||
const expiry =
|
const expiry = rawItem.alwaysOffered
|
||||||
cycleStart +
|
? 2051240400_000
|
||||||
rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour;
|
: cycleStart +
|
||||||
|
(rawItem.rotatedWeekly
|
||||||
|
? unixTimesInMs.week
|
||||||
|
: rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour);
|
||||||
const item: IItemManifest = {
|
const item: IItemManifest = {
|
||||||
StoreItem: rawItem.storeItem,
|
StoreItem: rawItem.storeItem,
|
||||||
ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
|
ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
|
||||||
Bin: "BIN_" + rawItem.bin,
|
Bin: "BIN_" + rawItem.bin,
|
||||||
QuantityMultiplier: 1,
|
QuantityMultiplier: rawItem.quantity,
|
||||||
Expiry: { $date: { $numberLong: expiry.toString() } },
|
Expiry: { $date: { $numberLong: expiry.toString() } },
|
||||||
AllowMultipurchase: false,
|
PurchaseQuantityLimit: rawItem.purchaseLimit,
|
||||||
|
RotatedWeekly: rawItem.rotatedWeekly,
|
||||||
|
AllowMultipurchase: rawItem.purchaseLimit !== 1,
|
||||||
Id: {
|
Id: {
|
||||||
$oid:
|
$oid:
|
||||||
((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
|
((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
|
||||||
@ -322,7 +352,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (rawItem.numRandomItemPrices) {
|
if (rawItem.numRandomItemPrices) {
|
||||||
item.ItemPrices = [];
|
item.ItemPrices ??= [];
|
||||||
for (let i = 0; i != rawItem.numRandomItemPrices; ++i) {
|
for (let i = 0; i != rawItem.numRandomItemPrices; ++i) {
|
||||||
let itemPrice: { type: string; count: IRange };
|
let itemPrice: { type: string; count: IRange };
|
||||||
do {
|
do {
|
||||||
@ -362,6 +392,14 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
info.ItemManifest.push(item);
|
info.ItemManifest.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (manifest.numItemsPerBin) {
|
||||||
|
info.ItemManifest.sort((a, b) => {
|
||||||
|
const aBin = parseInt(a.Bin.substring(4));
|
||||||
|
const bBin = parseInt(b.Bin.substring(4));
|
||||||
|
return aBin == bBin ? 0 : aBin < bBin ? +1 : -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Update vendor expiry
|
// Update vendor expiry
|
||||||
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
|
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
|
||||||
for (const offer of info.ItemManifest) {
|
for (const offer of info.ItemManifest) {
|
||||||
@ -371,21 +409,90 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
info.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
|
info.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
|
||||||
|
|
||||||
|
now += unixTimesInMs.hour;
|
||||||
}
|
}
|
||||||
return cacheEntry;
|
return cacheEntry;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isDev) {
|
if (args.dev) {
|
||||||
const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")!
|
if (
|
||||||
|
getCycleDuration(ExportVendors["/Lotus/Types/Game/VendorManifests/Hubs/TeshinHardModeVendorManifest"]) !=
|
||||||
|
unixTimesInMs.week
|
||||||
|
) {
|
||||||
|
logger.warn(`getCycleDuration self test failed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i != 2; ++i) {
|
||||||
|
const fullStock = !!i;
|
||||||
|
|
||||||
|
const ads = getVendorManifestByTypeName(
|
||||||
|
"/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
|
||||||
|
fullStock
|
||||||
|
)!.VendorInfo.ItemManifest;
|
||||||
|
if (
|
||||||
|
ads.length != 5 ||
|
||||||
|
ads[0].Bin != "BIN_4" ||
|
||||||
|
ads[1].Bin != "BIN_3" ||
|
||||||
|
ads[2].Bin != "BIN_2" ||
|
||||||
|
ads[3].Bin != "BIN_1" ||
|
||||||
|
ads[4].Bin != "BIN_0"
|
||||||
|
) {
|
||||||
|
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pall = getVendorManifestByTypeName(
|
||||||
|
"/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest",
|
||||||
|
fullStock
|
||||||
|
)!.VendorInfo.ItemManifest;
|
||||||
|
if (
|
||||||
|
pall.length != 5 ||
|
||||||
|
pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
|
||||||
|
pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" ||
|
||||||
|
pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" ||
|
||||||
|
pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" ||
|
||||||
|
pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack"
|
||||||
|
) {
|
||||||
|
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cms = getVendorManifestByTypeName(
|
||||||
|
"/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest",
|
||||||
|
false
|
||||||
|
)!.VendorInfo.ItemManifest;
|
||||||
|
if (
|
||||||
|
cms.length != 9 ||
|
||||||
|
cms[0].Bin != "BIN_2" ||
|
||||||
|
cms[8].Bin != "BIN_0" ||
|
||||||
|
cms.reduce((a, x) => a + (x.Bin == "BIN_2" ? 1 : 0), 0) < 2 ||
|
||||||
|
cms.reduce((a, x) => a + (x.Bin == "BIN_1" ? 1 : 0), 0) < 2 ||
|
||||||
|
cms.reduce((a, x) => a + (x.Bin == "BIN_0" ? 1 : 0), 0) < 4
|
||||||
|
) {
|
||||||
|
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const temple = getVendorManifestByTypeName(
|
||||||
|
"/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest",
|
||||||
|
false
|
||||||
|
)!.VendorInfo.ItemManifest;
|
||||||
|
if (!temple.find(x => x.StoreItem == "/Lotus/StoreItems/Types/Items/MiscItems/Kuva")) {
|
||||||
|
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest", false)!
|
||||||
.VendorInfo.ItemManifest;
|
.VendorInfo.ItemManifest;
|
||||||
if (
|
if (
|
||||||
ads.length != 5 ||
|
nakak.length != 10 ||
|
||||||
ads[0].Bin != "BIN_4" ||
|
nakak[0].StoreItem != "/Lotus/StoreItems/Upgrades/Skins/Ostron/RevenantMask" ||
|
||||||
ads[1].Bin != "BIN_3" ||
|
nakak[1].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumper" ||
|
||||||
ads[2].Bin != "BIN_2" ||
|
nakak[1].ItemPrices?.length != 4 ||
|
||||||
ads[3].Bin != "BIN_1" ||
|
nakak[2].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperMedium" ||
|
||||||
ads[4].Bin != "BIN_0"
|
nakak[2].ItemPrices?.length != 4 ||
|
||||||
|
nakak[3].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperLarge" ||
|
||||||
|
nakak[3].ItemPrices?.length != 4
|
||||||
|
// The remaining offers should be computed by weighted RNG.
|
||||||
) {
|
) {
|
||||||
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
|
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,18 @@ import {
|
|||||||
ISetShipCustomizationsRequest,
|
ISetShipCustomizationsRequest,
|
||||||
IShipDecorationsRequest,
|
IShipDecorationsRequest,
|
||||||
IShipDecorationsResponse,
|
IShipDecorationsResponse,
|
||||||
ISetPlacedDecoInfoRequest
|
ISetPlacedDecoInfoRequest,
|
||||||
|
TBootLocation
|
||||||
} from "@/src/types/shipTypes";
|
} from "@/src/types/shipTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { addShipDecorations, getInventory } from "./inventoryService";
|
import { addFusionTreasures, addShipDecorations, getInventory } from "./inventoryService";
|
||||||
import { config } from "./configService";
|
import { config } from "./configService";
|
||||||
import { Guild } from "../models/guildModel";
|
import { Guild } from "../models/guildModel";
|
||||||
import { hasGuildPermission } from "./guildService";
|
import { hasGuildPermission } from "./guildService";
|
||||||
import { GuildPermission } from "../types/guildTypes";
|
import { GuildPermission } from "../types/guildTypes";
|
||||||
|
import { ExportResources } from "warframe-public-export-plus";
|
||||||
|
import { RoomsType, TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes";
|
||||||
|
|
||||||
export const setShipCustomizations = async (
|
export const setShipCustomizations = async (
|
||||||
accountId: string,
|
accountId: string,
|
||||||
@ -58,7 +61,16 @@ export const handleSetShipDecorations = async (
|
|||||||
const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room);
|
const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room);
|
||||||
|
|
||||||
if (!roomToPlaceIn) {
|
if (!roomToPlaceIn) {
|
||||||
throw new Error("room not found");
|
throw new Error(`unknown room: ${placedDecoration.Room}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type);
|
||||||
|
if (!entry) {
|
||||||
|
throw new Error(`unknown deco type: ${placedDecoration.Type}`);
|
||||||
|
}
|
||||||
|
const [itemType, meta] = entry;
|
||||||
|
if (meta.capacityCost === undefined) {
|
||||||
|
throw new Error(`unknown deco type: ${placedDecoration.Type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (placedDecoration.MoveId) {
|
if (placedDecoration.MoveId) {
|
||||||
@ -82,7 +94,7 @@ export const handleSetShipDecorations = async (
|
|||||||
OldRoom: placedDecoration.OldRoom,
|
OldRoom: placedDecoration.OldRoom,
|
||||||
NewRoom: placedDecoration.Room,
|
NewRoom: placedDecoration.Room,
|
||||||
IsApartment: placedDecoration.IsApartment,
|
IsApartment: placedDecoration.IsApartment,
|
||||||
MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal
|
MaxCapacityIncrease: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,33 +107,44 @@ export const handleSetShipDecorations = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
oldRoom.PlacedDecos.pull({ _id: placedDecoration.MoveId });
|
oldRoom.PlacedDecos.pull({ _id: placedDecoration.MoveId });
|
||||||
|
oldRoom.MaxCapacity += meta.capacityCost;
|
||||||
|
|
||||||
const newDecoration = {
|
const newDecoration = {
|
||||||
Type: placedDecoration.Type,
|
Type: placedDecoration.Type,
|
||||||
Pos: placedDecoration.Pos,
|
Pos: placedDecoration.Pos,
|
||||||
Rot: placedDecoration.Rot,
|
Rot: placedDecoration.Rot,
|
||||||
Scale: placedDecoration.Scale,
|
Scale: placedDecoration.Scale,
|
||||||
|
Sockets: placedDecoration.Sockets,
|
||||||
_id: placedDecoration.MoveId
|
_id: placedDecoration.MoveId
|
||||||
};
|
};
|
||||||
|
|
||||||
//the new room is still roomToPlaceIn
|
//the new room is still roomToPlaceIn
|
||||||
roomToPlaceIn.PlacedDecos.push(newDecoration);
|
roomToPlaceIn.PlacedDecos.push(newDecoration);
|
||||||
|
roomToPlaceIn.MaxCapacity -= meta.capacityCost;
|
||||||
|
|
||||||
await personalRooms.save();
|
await personalRooms.save();
|
||||||
return {
|
return {
|
||||||
OldRoom: placedDecoration.OldRoom,
|
OldRoom: placedDecoration.OldRoom,
|
||||||
NewRoom: placedDecoration.Room,
|
NewRoom: placedDecoration.Room,
|
||||||
IsApartment: placedDecoration.IsApartment,
|
IsApartment: placedDecoration.IsApartment,
|
||||||
MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal
|
MaxCapacityIncrease: -meta.capacityCost
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (placedDecoration.RemoveId) {
|
if (placedDecoration.RemoveId) {
|
||||||
roomToPlaceIn.PlacedDecos.pull({ _id: placedDecoration.RemoveId });
|
const decoIndex = roomToPlaceIn.PlacedDecos.findIndex(x => x._id.equals(placedDecoration.RemoveId));
|
||||||
|
const deco = roomToPlaceIn.PlacedDecos[decoIndex];
|
||||||
|
roomToPlaceIn.PlacedDecos.splice(decoIndex, 1);
|
||||||
|
roomToPlaceIn.MaxCapacity += meta.capacityCost;
|
||||||
await personalRooms.save();
|
await personalRooms.save();
|
||||||
|
|
||||||
if (!config.unlockAllShipDecorations) {
|
if (!config.unlockAllShipDecorations) {
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
addShipDecorations(inventory, [{ ItemType: placedDecoration.Type, ItemCount: 1 }]);
|
if (deco.Sockets !== undefined) {
|
||||||
|
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
|
||||||
|
} else {
|
||||||
|
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
|
||||||
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,17 +152,20 @@ export const handleSetShipDecorations = async (
|
|||||||
DecoId: placedDecoration.RemoveId,
|
DecoId: placedDecoration.RemoveId,
|
||||||
Room: placedDecoration.Room,
|
Room: placedDecoration.Room,
|
||||||
IsApartment: placedDecoration.IsApartment,
|
IsApartment: placedDecoration.IsApartment,
|
||||||
MaxCapacityIncrease: 0
|
MaxCapacityIncrease: 0 // Client already implies the capacity being refunded.
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
if (!config.unlockAllShipDecorations) {
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
addShipDecorations(inventory, [{ ItemType: placedDecoration.Type, ItemCount: -1 }]);
|
|
||||||
await inventory.save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle capacity
|
if (!config.unlockAllShipDecorations) {
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0];
|
||||||
|
if (placedDecoration.Sockets !== undefined) {
|
||||||
|
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]);
|
||||||
|
} else {
|
||||||
|
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]);
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
}
|
||||||
|
|
||||||
//place decoration
|
//place decoration
|
||||||
const decoId = new Types.ObjectId();
|
const decoId = new Types.ObjectId();
|
||||||
@ -148,12 +174,32 @@ export const handleSetShipDecorations = async (
|
|||||||
Pos: placedDecoration.Pos,
|
Pos: placedDecoration.Pos,
|
||||||
Rot: placedDecoration.Rot,
|
Rot: placedDecoration.Rot,
|
||||||
Scale: placedDecoration.Scale,
|
Scale: placedDecoration.Scale,
|
||||||
|
Sockets: placedDecoration.Sockets,
|
||||||
_id: decoId
|
_id: decoId
|
||||||
});
|
});
|
||||||
|
roomToPlaceIn.MaxCapacity -= meta.capacityCost;
|
||||||
|
|
||||||
await personalRooms.save();
|
await personalRooms.save();
|
||||||
|
|
||||||
return { DecoId: decoId.toString(), Room: placedDecoration.Room, IsApartment: placedDecoration.IsApartment };
|
return {
|
||||||
|
DecoId: decoId.toString(),
|
||||||
|
Room: placedDecoration.Room,
|
||||||
|
IsApartment: placedDecoration.IsApartment,
|
||||||
|
MaxCapacityIncrease: -meta.capacityCost
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRoomsForBootLocation = (
|
||||||
|
personalRooms: TPersonalRoomsDatabaseDocument,
|
||||||
|
bootLocation: TBootLocation | undefined
|
||||||
|
): RoomsType[] => {
|
||||||
|
if (bootLocation == "SHOP") {
|
||||||
|
return personalRooms.TailorShop.Rooms;
|
||||||
|
}
|
||||||
|
if (bootLocation == "APARTMENT") {
|
||||||
|
return personalRooms.Apartment.Rooms;
|
||||||
|
}
|
||||||
|
return personalRooms.Ship.Rooms;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => {
|
export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => {
|
||||||
@ -170,14 +216,14 @@ export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlaced
|
|||||||
|
|
||||||
const personalRooms = await getPersonalRooms(accountId);
|
const personalRooms = await getPersonalRooms(accountId);
|
||||||
|
|
||||||
const room = personalRooms.Ship.Rooms.find(room => room.Name === req.Room);
|
const room = getRoomsForBootLocation(personalRooms, req.BootLocation).find(room => room.Name === req.Room);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
throw new Error("room not found");
|
throw new Error(`unknown room: ${req.Room}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const placedDeco = room.PlacedDecos.id(req.DecoId);
|
const placedDeco = room.PlacedDecos.id(req.DecoId);
|
||||||
if (!placedDeco) {
|
if (!placedDeco) {
|
||||||
throw new Error("deco not found");
|
throw new Error(`unknown deco id: ${req.DecoId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
placedDeco.PictureFrameInfo = req.PictureFrameInfo;
|
placedDeco.PictureFrameInfo = req.PictureFrameInfo;
|
||||||
|
@ -5,9 +5,17 @@ import { config } from "./configService";
|
|||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { app } from "../app";
|
import { app } from "../app";
|
||||||
import { AddressInfo } from "node:net";
|
import { AddressInfo } from "node:net";
|
||||||
|
import ws from "ws";
|
||||||
|
import { Account } from "../models/loginModel";
|
||||||
|
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService";
|
||||||
|
import { IDatabaseAccountJson } from "../types/loginTypes";
|
||||||
|
import { HydratedDocument } from "mongoose";
|
||||||
|
import { Agent, WebSocket as UnidiciWebSocket } from "undici";
|
||||||
|
|
||||||
let httpServer: http.Server | undefined;
|
let httpServer: http.Server | undefined;
|
||||||
let httpsServer: https.Server | undefined;
|
let httpsServer: https.Server | undefined;
|
||||||
|
let wsServer: ws.Server | undefined;
|
||||||
|
let wssServer: ws.Server | undefined;
|
||||||
|
|
||||||
const tlsOptions = {
|
const tlsOptions = {
|
||||||
key: fs.readFileSync("static/certs/key.pem"),
|
key: fs.readFileSync("static/certs/key.pem"),
|
||||||
@ -21,19 +29,65 @@ export const startWebServer = (): void => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
httpServer = http.createServer(app);
|
httpServer = http.createServer(app);
|
||||||
httpServer.listen(httpPort, () => {
|
httpServer.listen(httpPort, () => {
|
||||||
|
wsServer = new ws.Server({ server: httpServer });
|
||||||
|
wsServer.on("connection", wsOnConnect);
|
||||||
|
|
||||||
logger.info("HTTP server started on port " + httpPort);
|
logger.info("HTTP server started on port " + httpPort);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
httpsServer = https.createServer(tlsOptions, app);
|
httpsServer = https.createServer(tlsOptions, app);
|
||||||
httpsServer.listen(httpsPort, () => {
|
httpsServer.listen(httpsPort, () => {
|
||||||
|
wssServer = new ws.Server({ server: httpsServer });
|
||||||
|
wssServer.on("connection", wsOnConnect);
|
||||||
|
|
||||||
logger.info("HTTPS server started on port " + httpsPort);
|
logger.info("HTTPS server started on port " + httpsPort);
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort)
|
"Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void runWsSelfTest("wss", httpsPort).then(ok => {
|
||||||
|
if (!ok) {
|
||||||
|
logger.warn(`WSS self-test failed. The server may not actually be reachable at port ${httpsPort}.`);
|
||||||
|
if (process.platform == "win32") {
|
||||||
|
logger.warn(
|
||||||
|
`You can check who actually has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${httpsPort}).OwningProcess`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const runWsSelfTest = (protocol: "ws" | "wss", port: number): Promise<boolean> => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
// https://github.com/oven-sh/bun/issues/20547
|
||||||
|
if (process.versions.bun) {
|
||||||
|
const client = new WebSocket(`${protocol}://localhost:${port}/custom/selftest`, {
|
||||||
|
tls: { rejectUnauthorized: false }
|
||||||
|
} as unknown as string);
|
||||||
|
client.onmessage = (e): void => {
|
||||||
|
resolve(e.data == "SpaceNinjaServer");
|
||||||
|
};
|
||||||
|
client.onerror = client.onclose = (): void => {
|
||||||
|
resolve(false);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const agent = new Agent({ connect: { rejectUnauthorized: false } });
|
||||||
|
const client = new UnidiciWebSocket(`${protocol}://localhost:${port}/custom/selftest`, {
|
||||||
|
dispatcher: agent
|
||||||
|
});
|
||||||
|
client.onmessage = (e): void => {
|
||||||
|
resolve(e.data == "SpaceNinjaServer");
|
||||||
|
};
|
||||||
|
client.onerror = client.onclose = (): void => {
|
||||||
|
resolve(false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const getWebPorts = (): Record<"http" | "https", number | undefined> => {
|
export const getWebPorts = (): Record<"http" | "https", number | undefined> => {
|
||||||
return {
|
return {
|
||||||
http: (httpServer?.address() as AddressInfo | undefined)?.port,
|
http: (httpServer?.address() as AddressInfo | undefined)?.port,
|
||||||
@ -61,5 +115,182 @@ export const stopWebServer = async (): Promise<void> => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (wsServer) {
|
||||||
|
promises.push(
|
||||||
|
new Promise(resolve => {
|
||||||
|
wsServer!.close(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (wssServer) {
|
||||||
|
promises.push(
|
||||||
|
new Promise(resolve => {
|
||||||
|
wssServer!.close(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let lastWsid: number = 0;
|
||||||
|
|
||||||
|
interface IWsCustomData extends ws {
|
||||||
|
id?: number;
|
||||||
|
accountId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IWsMsgFromClient {
|
||||||
|
auth?: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
isRegister: boolean;
|
||||||
|
};
|
||||||
|
logout?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IWsMsgToClient {
|
||||||
|
//wsid?: number;
|
||||||
|
reload?: boolean;
|
||||||
|
ports?: {
|
||||||
|
http: number | undefined;
|
||||||
|
https: number | undefined;
|
||||||
|
};
|
||||||
|
config_reloaded?: boolean;
|
||||||
|
auth_succ?: {
|
||||||
|
id: string;
|
||||||
|
DisplayName: string;
|
||||||
|
Nonce: number;
|
||||||
|
};
|
||||||
|
auth_fail?: {
|
||||||
|
isRegister: boolean;
|
||||||
|
};
|
||||||
|
logged_out?: boolean;
|
||||||
|
update_inventory?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
|
||||||
|
if (req.url == "/custom/selftest") {
|
||||||
|
ws.send("SpaceNinjaServer");
|
||||||
|
ws.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(ws as IWsCustomData).id = ++lastWsid;
|
||||||
|
ws.send(JSON.stringify({ wsid: lastWsid }));
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
ws.on("message", async msg => {
|
||||||
|
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
|
||||||
|
if (data.auth) {
|
||||||
|
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
|
||||||
|
if (account) {
|
||||||
|
if (isCorrectPassword(data.auth.password, account.password)) {
|
||||||
|
if (!account.Nonce) {
|
||||||
|
account.ClientType = "webui";
|
||||||
|
account.Nonce = createNonce();
|
||||||
|
await (account as HydratedDocument<IDatabaseAccountJson>).save();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
account = null;
|
||||||
|
}
|
||||||
|
} else if (data.auth.isRegister) {
|
||||||
|
const name = await getUsernameFromEmail(data.auth.email);
|
||||||
|
account = await createAccount({
|
||||||
|
email: data.auth.email,
|
||||||
|
password: data.auth.password,
|
||||||
|
ClientType: "webui",
|
||||||
|
LastLogin: new Date(),
|
||||||
|
DisplayName: name,
|
||||||
|
Nonce: createNonce()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (account) {
|
||||||
|
(ws as IWsCustomData).accountId = account.id;
|
||||||
|
ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
auth_succ: {
|
||||||
|
id: account.id,
|
||||||
|
DisplayName: account.DisplayName,
|
||||||
|
Nonce: account.Nonce
|
||||||
|
}
|
||||||
|
} satisfies IWsMsgToClient)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
auth_fail: {
|
||||||
|
isRegister: data.auth.isRegister
|
||||||
|
}
|
||||||
|
} satisfies IWsMsgToClient)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.logout) {
|
||||||
|
const accountId = (ws as IWsCustomData).accountId;
|
||||||
|
(ws as IWsCustomData).accountId = undefined;
|
||||||
|
await Account.updateOne(
|
||||||
|
{
|
||||||
|
_id: accountId,
|
||||||
|
ClientType: "webui"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Nonce: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
|
||||||
|
const msg = JSON.stringify(data);
|
||||||
|
if (wsServer) {
|
||||||
|
for (const client of wsServer.clients) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wssServer) {
|
||||||
|
for (const client of wssServer.clients) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
|
||||||
|
const msg = JSON.stringify(data);
|
||||||
|
if (wsServer) {
|
||||||
|
for (const client of wsServer.clients) {
|
||||||
|
if ((client as IWsCustomData).accountId == accountId) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wssServer) {
|
||||||
|
for (const client of wssServer.clients) {
|
||||||
|
if ((client as IWsCustomData).accountId == accountId) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
|
||||||
|
const msg = JSON.stringify(data);
|
||||||
|
if (wsServer) {
|
||||||
|
for (const client of wsServer.clients) {
|
||||||
|
if ((client as IWsCustomData).id != wsid) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wssServer) {
|
||||||
|
for (const client of wssServer.clients) {
|
||||||
|
if ((client as IWsCustomData).id != wsid) {
|
||||||
|
client.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
|
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
|
||||||
|
import baro from "@/static/fixed_responses/worldState/baro.json";
|
||||||
|
import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json";
|
||||||
import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
|
import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
|
||||||
import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json";
|
import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json";
|
||||||
import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
|
import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
|
||||||
|
import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
|
||||||
import { buildConfig } from "@/src/services/buildConfigService";
|
import { buildConfig } from "@/src/services/buildConfigService";
|
||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { SRng } from "@/src/services/rngService";
|
import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService";
|
||||||
import { ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
|
import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
|
||||||
import {
|
import {
|
||||||
ICalendarDay,
|
ICalendarDay,
|
||||||
ICalendarEvent,
|
ICalendarEvent,
|
||||||
@ -16,10 +19,16 @@ import {
|
|||||||
ISortie,
|
ISortie,
|
||||||
ISortieMission,
|
ISortieMission,
|
||||||
ISyndicateMissionInfo,
|
ISyndicateMissionInfo,
|
||||||
IWorldState
|
ITmp,
|
||||||
|
IVoidStorm,
|
||||||
|
IVoidTrader,
|
||||||
|
IVoidTraderOffer,
|
||||||
|
IWorldState,
|
||||||
|
TCircuitGameMode
|
||||||
} from "../types/worldStateTypes";
|
} from "../types/worldStateTypes";
|
||||||
import { version_compare } from "../helpers/inventoryHelpers";
|
import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
import { DailyDeal, Fissure } from "../models/worldStateModel";
|
||||||
|
|
||||||
const sortieBosses = [
|
const sortieBosses = [
|
||||||
"SORTIE_BOSS_HYENA",
|
"SORTIE_BOSS_HYENA",
|
||||||
@ -93,7 +102,7 @@ const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">,
|
|||||||
SORTIE_BOSS_VOR: "SolNode108"
|
SORTIE_BOSS_VOR: "SolNode108"
|
||||||
};
|
};
|
||||||
|
|
||||||
const eidolonJobs = [
|
const eidolonJobs: readonly string[] = [
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab",
|
||||||
@ -109,14 +118,14 @@ const eidolonJobs = [
|
|||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc"
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc"
|
||||||
];
|
];
|
||||||
|
|
||||||
const eidolonNarmerJobs = [
|
const eidolonNarmerJobs: readonly string[] = [
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AssassinateBountyAss",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AssassinateBountyAss",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft",
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft",
|
||||||
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib"
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib"
|
||||||
];
|
];
|
||||||
|
|
||||||
const venusJobs = [
|
const venusJobs: readonly string[] = [
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery",
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery",
|
||||||
@ -142,14 +151,14 @@ const venusJobs = [
|
|||||||
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobSpy"
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobSpy"
|
||||||
];
|
];
|
||||||
|
|
||||||
const venusNarmerJobs = [
|
const venusNarmerJobs: readonly string[] = [
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobAssassinate",
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobAssassinate",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate",
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense",
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense",
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation"
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation"
|
||||||
];
|
];
|
||||||
|
|
||||||
const microplanetJobs = [
|
const microplanetJobs: readonly string[] = [
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty",
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty",
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty",
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty",
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty",
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty",
|
||||||
@ -159,7 +168,7 @@ const microplanetJobs = [
|
|||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty"
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty"
|
||||||
];
|
];
|
||||||
|
|
||||||
const microplanetEndlessJobs = [
|
const microplanetEndlessJobs: readonly string[] = [
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty",
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty",
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty",
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty",
|
||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
|
||||||
@ -364,7 +373,7 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
|
|||||||
hardWeekly: syndicate.weeklyChallenges!.filter(x =>
|
hardWeekly: syndicate.weeklyChallenges!.filter(x =>
|
||||||
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
|
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
|
||||||
),
|
),
|
||||||
hasWeeklyPermanent: !!syndicate.weeklyChallenges!.find(x =>
|
hasWeeklyPermanent: syndicate.weeklyChallenges!.some(x =>
|
||||||
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
|
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -453,19 +462,44 @@ const pushWeeklyActs = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateXpAmounts = (rng: SRng, stageCount: number, minXp: number, maxXp: number): number[] => {
|
||||||
|
const step = minXp < 1000 ? 1 : 10;
|
||||||
|
const totalDeciXp = rng.randomInt(minXp / step, maxXp / step);
|
||||||
|
const xpAmounts: number[] = [];
|
||||||
|
if (stageCount < 4) {
|
||||||
|
const perStage = Math.ceil(totalDeciXp / stageCount) * step;
|
||||||
|
for (let i = 0; i != stageCount; ++i) {
|
||||||
|
xpAmounts.push(perStage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const perStage = Math.ceil(Math.round(totalDeciXp * 0.667) / (stageCount - 1)) * step;
|
||||||
|
for (let i = 0; i != stageCount - 1; ++i) {
|
||||||
|
xpAmounts.push(perStage);
|
||||||
|
}
|
||||||
|
xpAmounts.push(Math.ceil(totalDeciXp * 0.332) * step);
|
||||||
|
}
|
||||||
|
return xpAmounts;
|
||||||
|
};
|
||||||
|
// Test vectors:
|
||||||
|
//console.log(generateXpAmounts(new SRng(1337n), 5, 5000, 5000)); // [840, 840, 840, 840, 1660]
|
||||||
|
//console.log(generateXpAmounts(new SRng(1337n), 3, 40, 40)); // [14, 14, 14]
|
||||||
|
//console.log(generateXpAmounts(new SRng(1337n), 5, 150, 150)); // [25, 25, 25, 25, 50]
|
||||||
|
//console.log(generateXpAmounts(new SRng(1337n), 4, 10, 10)); // [2, 2, 2, 4]
|
||||||
|
//console.log(generateXpAmounts(new SRng(1337n), 4, 15, 15)); // [4, 4, 4, 5]
|
||||||
|
//console.log(generateXpAmounts(new SRng(1337n), 4, 20, 20)); // [5, 5, 5, 7]
|
||||||
|
|
||||||
export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], bountyCycle: number): void => {
|
export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], bountyCycle: number): void => {
|
||||||
const table = String.fromCharCode(65 + (bountyCycle % 3));
|
const table = String.fromCharCode(65 + (bountyCycle % 3));
|
||||||
const vaultTable = String.fromCharCode(65 + ((bountyCycle + 1) % 3));
|
const vaultTable = String.fromCharCode(65 + ((bountyCycle + 1) % 3));
|
||||||
const deimosDTable = String.fromCharCode(65 + (bountyCycle % 2));
|
const deimosDTable = String.fromCharCode(65 + (bountyCycle % 2));
|
||||||
|
|
||||||
// TODO: xpAmounts need to be calculated based on the jobType somehow?
|
|
||||||
|
|
||||||
const seed = new SRng(bountyCycle).randomInt(0, 100_000);
|
const seed = new SRng(bountyCycle).randomInt(0, 100_000);
|
||||||
const bountyCycleStart = bountyCycle * 9000000;
|
const bountyCycleStart = bountyCycle * 9000000;
|
||||||
const bountyCycleEnd = bountyCycleStart + 9000000;
|
const bountyCycleEnd = bountyCycleStart + 9000000;
|
||||||
|
|
||||||
{
|
{
|
||||||
const rng = new SRng(seed);
|
const rng = new SRng(seed);
|
||||||
|
const pool = [...eidolonJobs];
|
||||||
syndicateMissions.push({
|
syndicateMissions.push({
|
||||||
_id: {
|
_id: {
|
||||||
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
|
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
|
||||||
@ -477,47 +511,47 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
|||||||
Nodes: [],
|
Nodes: [],
|
||||||
Jobs: [
|
Jobs: [
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(eidolonJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${table}Rewards`,
|
||||||
masteryReq: 0,
|
masteryReq: 0,
|
||||||
minEnemyLevel: 5,
|
minEnemyLevel: 5,
|
||||||
maxEnemyLevel: 15,
|
maxEnemyLevel: 15,
|
||||||
xpAmounts: [430, 430, 430]
|
xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(eidolonJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${table}Rewards`,
|
||||||
masteryReq: 1,
|
masteryReq: 1,
|
||||||
minEnemyLevel: 10,
|
minEnemyLevel: 10,
|
||||||
maxEnemyLevel: 30,
|
maxEnemyLevel: 30,
|
||||||
xpAmounts: [620, 620, 620]
|
xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(eidolonJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${table}Rewards`,
|
||||||
masteryReq: 2,
|
masteryReq: 2,
|
||||||
minEnemyLevel: 20,
|
minEnemyLevel: 20,
|
||||||
maxEnemyLevel: 40,
|
maxEnemyLevel: 40,
|
||||||
xpAmounts: [670, 670, 670, 990]
|
xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(eidolonJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${table}Rewards`,
|
||||||
masteryReq: 3,
|
masteryReq: 3,
|
||||||
minEnemyLevel: 30,
|
minEnemyLevel: 30,
|
||||||
maxEnemyLevel: 50,
|
maxEnemyLevel: 50,
|
||||||
xpAmounts: [570, 570, 570, 570, 1110]
|
xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(eidolonJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
|
||||||
masteryReq: 5,
|
masteryReq: 5,
|
||||||
minEnemyLevel: 40,
|
minEnemyLevel: 40,
|
||||||
maxEnemyLevel: 60,
|
maxEnemyLevel: 60,
|
||||||
xpAmounts: [740, 740, 740, 740, 1450]
|
xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(eidolonJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
|
||||||
masteryReq: 10,
|
masteryReq: 10,
|
||||||
minEnemyLevel: 100,
|
minEnemyLevel: 100,
|
||||||
@ -530,7 +564,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
|||||||
masteryReq: 0,
|
masteryReq: 0,
|
||||||
minEnemyLevel: 50,
|
minEnemyLevel: 50,
|
||||||
maxEnemyLevel: 70,
|
maxEnemyLevel: 70,
|
||||||
xpAmounts: [840, 840, 840, 840, 1650]
|
xpAmounts: generateXpAmounts(rng, 5, 4500, 5000)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -538,6 +572,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
|||||||
|
|
||||||
{
|
{
|
||||||
const rng = new SRng(seed);
|
const rng = new SRng(seed);
|
||||||
|
const pool = [...venusJobs];
|
||||||
syndicateMissions.push({
|
syndicateMissions.push({
|
||||||
_id: {
|
_id: {
|
||||||
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
|
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
|
||||||
@ -549,47 +584,47 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
|||||||
Nodes: [],
|
Nodes: [],
|
||||||
Jobs: [
|
Jobs: [
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(venusJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`,
|
||||||
masteryReq: 0,
|
masteryReq: 0,
|
||||||
minEnemyLevel: 5,
|
minEnemyLevel: 5,
|
||||||
maxEnemyLevel: 15,
|
maxEnemyLevel: 15,
|
||||||
xpAmounts: [340, 340, 340]
|
xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(venusJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`,
|
||||||
masteryReq: 1,
|
masteryReq: 1,
|
||||||
minEnemyLevel: 10,
|
minEnemyLevel: 10,
|
||||||
maxEnemyLevel: 30,
|
maxEnemyLevel: 30,
|
||||||
xpAmounts: [660, 660, 660]
|
xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(venusJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`,
|
||||||
masteryReq: 2,
|
masteryReq: 2,
|
||||||
minEnemyLevel: 20,
|
minEnemyLevel: 20,
|
||||||
maxEnemyLevel: 40,
|
maxEnemyLevel: 40,
|
||||||
xpAmounts: [610, 610, 610, 900]
|
xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(venusJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`,
|
||||||
masteryReq: 3,
|
masteryReq: 3,
|
||||||
minEnemyLevel: 30,
|
minEnemyLevel: 30,
|
||||||
maxEnemyLevel: 50,
|
maxEnemyLevel: 50,
|
||||||
xpAmounts: [600, 600, 600, 600, 1170]
|
xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(venusJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
|
||||||
masteryReq: 5,
|
masteryReq: 5,
|
||||||
minEnemyLevel: 40,
|
minEnemyLevel: 40,
|
||||||
maxEnemyLevel: 60,
|
maxEnemyLevel: 60,
|
||||||
xpAmounts: [690, 690, 690, 690, 1350]
|
xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(venusJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
|
||||||
masteryReq: 10,
|
masteryReq: 10,
|
||||||
minEnemyLevel: 100,
|
minEnemyLevel: 100,
|
||||||
@ -602,7 +637,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
|||||||
masteryReq: 0,
|
masteryReq: 0,
|
||||||
minEnemyLevel: 50,
|
minEnemyLevel: 50,
|
||||||
maxEnemyLevel: 70,
|
maxEnemyLevel: 70,
|
||||||
xpAmounts: [780, 780, 780, 780, 1540]
|
xpAmounts: generateXpAmounts(rng, 5, 4500, 5000)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -610,6 +645,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
|||||||
|
|
||||||
{
|
{
|
||||||
const rng = new SRng(seed);
|
const rng = new SRng(seed);
|
||||||
|
const pool = [...microplanetJobs];
|
||||||
syndicateMissions.push({
|
syndicateMissions.push({
|
||||||
_id: {
|
_id: {
|
||||||
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
|
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
|
||||||
@ -621,20 +657,20 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
|||||||
Nodes: [],
|
Nodes: [],
|
||||||
Jobs: [
|
Jobs: [
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(microplanetJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${table}Rewards`,
|
||||||
masteryReq: 0,
|
masteryReq: 0,
|
||||||
minEnemyLevel: 5,
|
minEnemyLevel: 5,
|
||||||
maxEnemyLevel: 15,
|
maxEnemyLevel: 15,
|
||||||
xpAmounts: [5, 5, 5]
|
xpAmounts: generateXpAmounts(rng, 3, 12, 18)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(microplanetJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${table}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${table}Rewards`,
|
||||||
masteryReq: 1,
|
masteryReq: 1,
|
||||||
minEnemyLevel: 15,
|
minEnemyLevel: 15,
|
||||||
maxEnemyLevel: 25,
|
maxEnemyLevel: 25,
|
||||||
xpAmounts: [12, 12, 12]
|
xpAmounts: generateXpAmounts(rng, 3, 24, 36)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(microplanetEndlessJobs),
|
jobType: rng.randomElement(microplanetEndlessJobs),
|
||||||
@ -646,23 +682,23 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
|||||||
xpAmounts: [14, 14, 14]
|
xpAmounts: [14, 14, 14]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(microplanetJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${deimosDTable}Rewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${deimosDTable}Rewards`,
|
||||||
masteryReq: 2,
|
masteryReq: 2,
|
||||||
minEnemyLevel: 30,
|
minEnemyLevel: 30,
|
||||||
maxEnemyLevel: 40,
|
maxEnemyLevel: 40,
|
||||||
xpAmounts: [17, 17, 17, 25]
|
xpAmounts: generateXpAmounts(rng, 4, 72, 88)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(microplanetJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
|
||||||
masteryReq: 3,
|
masteryReq: 3,
|
||||||
minEnemyLevel: 40,
|
minEnemyLevel: 40,
|
||||||
maxEnemyLevel: 60,
|
maxEnemyLevel: 60,
|
||||||
xpAmounts: [22, 22, 22, 22, 43]
|
xpAmounts: generateXpAmounts(rng, 5, 115, 135)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
jobType: rng.randomElement(microplanetJobs),
|
jobType: rng.randomElementPop(pool),
|
||||||
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
|
||||||
masteryReq: 10,
|
masteryReq: 10,
|
||||||
minEnemyLevel: 100,
|
minEnemyLevel: 100,
|
||||||
@ -939,6 +975,61 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Not very faithful, but to avoid the same node coming up back-to-back (which is not valid), I've split these into 2 arrays which we're alternating between.
|
||||||
|
|
||||||
|
const voidStormMissionsA = {
|
||||||
|
VoidT1: ["CrewBattleNode519", "CrewBattleNode518", "CrewBattleNode515", "CrewBattleNode503"],
|
||||||
|
VoidT2: ["CrewBattleNode501", "CrewBattleNode534", "CrewBattleNode530"],
|
||||||
|
VoidT3: ["CrewBattleNode521", "CrewBattleNode516"],
|
||||||
|
VoidT4: [
|
||||||
|
"CrewBattleNode555",
|
||||||
|
"CrewBattleNode553",
|
||||||
|
"CrewBattleNode554",
|
||||||
|
"CrewBattleNode539",
|
||||||
|
"CrewBattleNode531",
|
||||||
|
"CrewBattleNode527"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const voidStormMissionsB = {
|
||||||
|
VoidT1: ["CrewBattleNode509", "CrewBattleNode522", "CrewBattleNode511", "CrewBattleNode512"],
|
||||||
|
VoidT2: ["CrewBattleNode535", "CrewBattleNode533"],
|
||||||
|
VoidT3: ["CrewBattleNode524", "CrewBattleNode525"],
|
||||||
|
VoidT4: [
|
||||||
|
"CrewBattleNode542",
|
||||||
|
"CrewBattleNode538",
|
||||||
|
"CrewBattleNode543",
|
||||||
|
"CrewBattleNode536",
|
||||||
|
"CrewBattleNode550",
|
||||||
|
"CrewBattleNode529"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
|
||||||
|
const activation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute;
|
||||||
|
const expiry = activation + 90 * unixTimesInMs.minute;
|
||||||
|
let accum = 0;
|
||||||
|
const rng = new SRng(new SRng(hour).randomInt(0, 100_000));
|
||||||
|
const voidStormMissions = structuredClone(hour & 1 ? voidStormMissionsA : voidStormMissionsB);
|
||||||
|
for (const tier of ["VoidT1", "VoidT1", "VoidT2", "VoidT3", "VoidT4", "VoidT4"] as const) {
|
||||||
|
const idx = rng.randomInt(0, voidStormMissions[tier].length - 1);
|
||||||
|
const node = voidStormMissions[tier][idx];
|
||||||
|
voidStormMissions[tier].splice(idx, 1);
|
||||||
|
arr.push({
|
||||||
|
_id: {
|
||||||
|
$oid:
|
||||||
|
((activation / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
|
||||||
|
"0321e89b" +
|
||||||
|
(accum++).toString().padStart(8, "0")
|
||||||
|
},
|
||||||
|
Node: node,
|
||||||
|
Activation: { $date: { $numberLong: activation.toString() } },
|
||||||
|
Expiry: { $date: { $numberLong: expiry.toString() } },
|
||||||
|
ActiveMissionTier: tier
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const doesTimeSatsifyConstraints = (timeSecs: number): boolean => {
|
const doesTimeSatsifyConstraints = (timeSecs: number): boolean => {
|
||||||
if (config.worldState?.eidolonOverride) {
|
if (config.worldState?.eidolonOverride) {
|
||||||
const eidolonEpoch = 1391992660;
|
const eidolonEpoch = 1391992660;
|
||||||
@ -986,6 +1077,27 @@ const doesTimeSatsifyConstraints = (timeSecs: number): boolean => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.worldState?.duviriOverride) {
|
||||||
|
const duviriMoods = ["sorrow", "fear", "joy", "anger", "envy"];
|
||||||
|
const desiredMood = duviriMoods.indexOf(config.worldState.duviriOverride);
|
||||||
|
if (desiredMood == -1) {
|
||||||
|
logger.warn(`ignoring invalid config value for worldState.duviriOverride`, {
|
||||||
|
value: config.worldState.duviriOverride,
|
||||||
|
valid_values: duviriMoods
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const moodIndex = Math.trunc(timeSecs / 7200);
|
||||||
|
const moodStart = moodIndex * 7200;
|
||||||
|
const moodEnd = moodStart + 7200;
|
||||||
|
if (
|
||||||
|
moodIndex % 5 != desiredMood ||
|
||||||
|
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, moodEnd * 1000)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1007,20 +1119,20 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
Alerts: [],
|
Alerts: [],
|
||||||
Sorties: [],
|
Sorties: [],
|
||||||
LiteSorties: [],
|
LiteSorties: [],
|
||||||
|
ActiveMissions: [],
|
||||||
GlobalUpgrades: [],
|
GlobalUpgrades: [],
|
||||||
|
VoidTraders: [],
|
||||||
|
VoidStorms: [],
|
||||||
|
DailyDeals: [],
|
||||||
EndlessXpChoices: [],
|
EndlessXpChoices: [],
|
||||||
KnownCalendarSeasons: [],
|
KnownCalendarSeasons: [],
|
||||||
...staticWorldState,
|
...staticWorldState,
|
||||||
SyndicateMissions: [...staticWorldState.SyndicateMissions]
|
SyndicateMissions: [...staticWorldState.SyndicateMissions]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Omit void fissures for versions prior to Dante Unbound to avoid script errors.
|
// Old versions seem to really get hung up on not being able to load these.
|
||||||
if (buildLabel && version_compare(buildLabel, "2024.03.24.20.00") < 0) {
|
if (buildLabel && version_compare(buildLabel, "2017.10.12.17.04") < 0) {
|
||||||
worldState.ActiveMissions = [];
|
worldState.PVPChallengeInstances = [];
|
||||||
if (version_compare(buildLabel, "2017.10.12.17.04") < 0) {
|
|
||||||
// Old versions seem to really get hung up on not being able to load these.
|
|
||||||
worldState.PVPChallengeInstances = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.worldState?.starDays) {
|
if (config.worldState?.starDays) {
|
||||||
@ -1039,6 +1151,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
Node: "SolarisUnitedHub1"
|
Node: "SolarisUnitedHub1"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// The client gets kinda confused when multiple goals have the same tag, so considering these mutually exclusive.
|
||||||
|
if (config.worldState?.galleonOfGhouls == 1) {
|
||||||
|
worldState.Goals.push({
|
||||||
|
_id: { $oid: "6814ddf00000000000000000" },
|
||||||
|
Activation: { $date: { $numberLong: "1746198000000" } },
|
||||||
|
Expiry: { $date: { $numberLong: "2000000000000" } },
|
||||||
|
Count: 0,
|
||||||
|
Goal: 1,
|
||||||
|
Success: 0,
|
||||||
|
Personal: true,
|
||||||
|
Bounty: true,
|
||||||
|
ClampNodeScores: true,
|
||||||
|
Node: "EventNode19",
|
||||||
|
MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlert",
|
||||||
|
Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
|
||||||
|
Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
|
||||||
|
Tag: "GalleonRobbery",
|
||||||
|
Reward: {
|
||||||
|
items: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/Weapons/GrnChainSawTonfaBlueprint",
|
||||||
|
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (config.worldState?.galleonOfGhouls == 2) {
|
||||||
|
worldState.Goals.push({
|
||||||
|
_id: { $oid: "681e18700000000000000000" },
|
||||||
|
Activation: { $date: { $numberLong: "1746802800000" } },
|
||||||
|
Expiry: { $date: { $numberLong: "2000000000000" } },
|
||||||
|
Count: 0,
|
||||||
|
Goal: 1,
|
||||||
|
Success: 0,
|
||||||
|
Personal: true,
|
||||||
|
Bounty: true,
|
||||||
|
ClampNodeScores: true,
|
||||||
|
Node: "EventNode28",
|
||||||
|
MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB",
|
||||||
|
Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
|
||||||
|
Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
|
||||||
|
Tag: "GalleonRobbery",
|
||||||
|
Reward: {
|
||||||
|
items: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/Weapons/MortiforShieldAndSwordBlueprint",
|
||||||
|
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (config.worldState?.galleonOfGhouls == 3) {
|
||||||
|
worldState.Goals.push({
|
||||||
|
_id: { $oid: "682752f00000000000000000" },
|
||||||
|
Activation: { $date: { $numberLong: "1747407600000" } },
|
||||||
|
Expiry: { $date: { $numberLong: "2000000000000" } },
|
||||||
|
Count: 0,
|
||||||
|
Goal: 1,
|
||||||
|
Success: 0,
|
||||||
|
Personal: true,
|
||||||
|
Bounty: true,
|
||||||
|
ClampNodeScores: true,
|
||||||
|
Node: "EventNode19",
|
||||||
|
MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertC",
|
||||||
|
Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
|
||||||
|
Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
|
||||||
|
Tag: "GalleonRobbery",
|
||||||
|
Reward: {
|
||||||
|
items: [
|
||||||
|
"/Lotus/Types/StoreItems/Packages/EventCatalystReactorBundle",
|
||||||
|
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Nightwave Challenges
|
// Nightwave Challenges
|
||||||
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
|
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
|
||||||
@ -1139,6 +1322,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Baro
|
||||||
|
{
|
||||||
|
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
|
||||||
|
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
|
||||||
|
const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
|
||||||
|
const baroEnd = baroStart + unixTimesInMs.day * 14;
|
||||||
|
const baroNode = ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4];
|
||||||
|
const vt: IVoidTrader = {
|
||||||
|
_id: { $oid: ((baroStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "493c96d6067610bc" },
|
||||||
|
Activation: { $date: { $numberLong: baroActualStart.toString() } },
|
||||||
|
Expiry: { $date: { $numberLong: baroEnd.toString() } },
|
||||||
|
Character: "Baro'Ki Teel",
|
||||||
|
Node: baroNode,
|
||||||
|
Manifest: []
|
||||||
|
};
|
||||||
|
worldState.VoidTraders.push(vt);
|
||||||
|
if (isBeforeNextExpectedWorldStateRefresh(timeMs, baroActualStart)) {
|
||||||
|
vt.Manifest = [];
|
||||||
|
if (config.baroFullyStocked) {
|
||||||
|
for (const armorSet of baro.armorSets) {
|
||||||
|
if (Array.isArray(armorSet[0])) {
|
||||||
|
for (const set of armorSet as IVoidTraderOffer[][]) {
|
||||||
|
for (const item of set) {
|
||||||
|
vt.Manifest.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const item of armorSet as IVoidTraderOffer[]) {
|
||||||
|
vt.Manifest.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const item of baro.rest) {
|
||||||
|
vt.Manifest.push(item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const rng = new SRng(new SRng(baroIndex).randomInt(0, 100_000));
|
||||||
|
// TOVERIFY: Constraint for upgrades amount?
|
||||||
|
// TOVERIFY: Constraint for weapon amount?
|
||||||
|
// TOVERIFY: Constraint for relics amount?
|
||||||
|
let armorSet = rng.randomElement(baro.armorSets)!;
|
||||||
|
if (Array.isArray(armorSet[0])) {
|
||||||
|
armorSet = rng.randomElement(baro.armorSets)!;
|
||||||
|
}
|
||||||
|
while (vt.Manifest.length + armorSet.length < 31) {
|
||||||
|
const item = rng.randomElement(baro.rest)!;
|
||||||
|
if (vt.Manifest.indexOf(item) == -1) {
|
||||||
|
const set = baro.allIfAny.find(set => set.indexOf(item.ItemType) != -1);
|
||||||
|
if (set) {
|
||||||
|
for (const itemType of set) {
|
||||||
|
vt.Manifest.push(baro.rest.find(x => x.ItemType == itemType)!);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vt.Manifest.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const overflow = 31 - (vt.Manifest.length + armorSet.length);
|
||||||
|
if (overflow > 0) {
|
||||||
|
vt.Manifest.splice(0, overflow);
|
||||||
|
}
|
||||||
|
for (const armor of armorSet) {
|
||||||
|
vt.Manifest.push(armor as IVoidTraderOffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const item of baro.evergreen) {
|
||||||
|
vt.Manifest.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
|
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
|
||||||
{
|
{
|
||||||
const rollover = getSortieTime(day);
|
const rollover = getSortieTime(day);
|
||||||
@ -1204,12 +1458,38 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
worldState.KnownCalendarSeasons.push(getCalendarSeason(week + 1));
|
worldState.KnownCalendarSeasons.push(getCalendarSeason(week + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sentient Anomaly cycling every 30 minutes
|
// Void Storms
|
||||||
|
const hour = Math.trunc(timeMs / unixTimesInMs.hour);
|
||||||
|
const overLastHourStormExpiry = hour * unixTimesInMs.hour + 10 * unixTimesInMs.minute;
|
||||||
|
const thisHourStormActivation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute;
|
||||||
|
if (overLastHourStormExpiry > timeMs) {
|
||||||
|
pushVoidStorms(worldState.VoidStorms, hour - 2);
|
||||||
|
}
|
||||||
|
pushVoidStorms(worldState.VoidStorms, hour - 1);
|
||||||
|
if (isBeforeNextExpectedWorldStateRefresh(timeMs, thisHourStormActivation)) {
|
||||||
|
pushVoidStorms(worldState.VoidStorms, hour);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sentient Anomaly + Xtra Cheese cycles
|
||||||
const halfHour = Math.trunc(timeMs / (unixTimesInMs.hour / 2));
|
const halfHour = Math.trunc(timeMs / (unixTimesInMs.hour / 2));
|
||||||
const tmp = {
|
const hourInSeconds = 3600;
|
||||||
|
const cheeseInterval = hourInSeconds * 8;
|
||||||
|
const cheeseDuration = hourInSeconds * 2;
|
||||||
|
const cheeseIndex = Math.trunc(timeSecs / cheeseInterval);
|
||||||
|
let cheeseStart = cheeseIndex * cheeseInterval;
|
||||||
|
let cheeseEnd = cheeseStart + cheeseDuration;
|
||||||
|
let cheeseNext = (cheeseIndex + 1) * cheeseInterval;
|
||||||
|
// Live servers only update the start time once it happens, which makes the
|
||||||
|
// client show a negative countdown during off-hours. Optionally adjust the
|
||||||
|
// times so the next activation is always in the future.
|
||||||
|
if (config.unfaithfulBugFixes?.fixXtraCheeseTimer && timeSecs >= cheeseEnd) {
|
||||||
|
cheeseStart = cheeseNext;
|
||||||
|
cheeseEnd = cheeseStart + cheeseDuration;
|
||||||
|
cheeseNext += cheeseInterval;
|
||||||
|
}
|
||||||
|
const tmp: ITmp = {
|
||||||
cavabegin: "1690761600",
|
cavabegin: "1690761600",
|
||||||
PurchasePlatformLockEnabled: true,
|
PurchasePlatformLockEnabled: true,
|
||||||
tcsn: true,
|
|
||||||
pgr: {
|
pgr: {
|
||||||
ts: "1732572900",
|
ts: "1732572900",
|
||||||
en: "CUSTOM DECALS @ ZEVILA",
|
en: "CUSTOM DECALS @ ZEVILA",
|
||||||
@ -1230,13 +1510,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
},
|
},
|
||||||
ennnd: true,
|
ennnd: true,
|
||||||
mbrt: true,
|
mbrt: true,
|
||||||
|
fbst: {
|
||||||
|
a: cheeseStart,
|
||||||
|
e: cheeseEnd,
|
||||||
|
n: cheeseNext
|
||||||
|
},
|
||||||
sfn: [550, 553, 554, 555][halfHour % 4]
|
sfn: [550, 553, 554, 555][halfHour % 4]
|
||||||
};
|
};
|
||||||
|
if (Array.isArray(config.worldState?.circuitGameModes)) {
|
||||||
|
tmp.edg = config.worldState.circuitGameModes as TCircuitGameMode[];
|
||||||
|
}
|
||||||
worldState.Tmp = JSON.stringify(tmp);
|
worldState.Tmp = JSON.stringify(tmp);
|
||||||
|
|
||||||
return worldState;
|
return worldState;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const populateFissures = async (worldState: IWorldState): Promise<void> => {
|
||||||
|
if (config.worldState?.allTheFissures) {
|
||||||
|
let i = 0;
|
||||||
|
for (const [tier, nodes] of Object.entries(fissureMissions)) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
const meta = ExportRegions[node];
|
||||||
|
worldState.ActiveMissions.push({
|
||||||
|
_id: { $oid: (i++).toString().padStart(8, "0") + "8e0c70ba050f1eb7" },
|
||||||
|
Region: meta.systemIndex + 1,
|
||||||
|
Seed: 1337,
|
||||||
|
Activation: { $date: { $numberLong: "1000000000000" } },
|
||||||
|
Expiry: { $date: { $numberLong: "2000000000000" } },
|
||||||
|
Node: node,
|
||||||
|
MissionType: eMissionType[meta.missionIndex].tag,
|
||||||
|
Modifier: tier,
|
||||||
|
Hard: config.worldState.allTheFissures == "hard"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const fissures = await Fissure.find({});
|
||||||
|
for (const fissure of fissures) {
|
||||||
|
const meta = ExportRegions[fissure.Node];
|
||||||
|
worldState.ActiveMissions.push({
|
||||||
|
_id: toOid(fissure._id),
|
||||||
|
Region: meta.systemIndex + 1,
|
||||||
|
Seed: 1337,
|
||||||
|
Activation: toMongoDate(fissure.Activation),
|
||||||
|
Expiry: toMongoDate(fissure.Expiry),
|
||||||
|
Node: fissure.Node,
|
||||||
|
MissionType: eMissionType[meta.missionIndex].tag,
|
||||||
|
Modifier: fissure.Modifier,
|
||||||
|
Hard: fissure.Hard
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const populateDailyDeal = async (worldState: IWorldState): Promise<void> => {
|
||||||
|
const dailyDeals = await DailyDeal.find({});
|
||||||
|
for (const dailyDeal of dailyDeals) {
|
||||||
|
if (dailyDeal.Expiry.getTime() > Date.now()) {
|
||||||
|
worldState.DailyDeals.push({
|
||||||
|
StoreItem: dailyDeal.StoreItem,
|
||||||
|
Activation: toMongoDate(dailyDeal.Activation),
|
||||||
|
Expiry: toMongoDate(dailyDeal.Expiry),
|
||||||
|
Discount: dailyDeal.Discount,
|
||||||
|
OriginalPrice: dailyDeal.OriginalPrice,
|
||||||
|
SalePrice: dailyDeal.SalePrice,
|
||||||
|
AmountTotal: Math.round(dailyDeal.AmountTotal * (config.worldState?.darvoStockMultiplier ?? 1)),
|
||||||
|
AmountSold: dailyDeal.AmountSold
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const idToBountyCycle = (id: string): number => {
|
export const idToBountyCycle = (id: string): number => {
|
||||||
return Math.trunc((parseInt(id.substring(0, 8), 16) * 1000) / 9000_000);
|
return Math.trunc((parseInt(id.substring(0, 8), 16) * 1000) / 9000_000);
|
||||||
};
|
};
|
||||||
@ -1364,3 +1708,92 @@ const nightwaveTagToSeason: Record<string, number> = {
|
|||||||
RadioLegionIntermissionSyndicate: 1, // Intermission I
|
RadioLegionIntermissionSyndicate: 1, // Intermission I
|
||||||
RadioLegionSyndicate: 0 // The Wolf of Saturn Six
|
RadioLegionSyndicate: 0 // The Wolf of Saturn Six
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateFissures = async (): Promise<void> => {
|
||||||
|
const fissures = await Fissure.find();
|
||||||
|
|
||||||
|
const activeNodes = new Set<string>();
|
||||||
|
const tierToFurthestExpiry: Record<string, number> = {
|
||||||
|
VoidT1: 0,
|
||||||
|
VoidT2: 0,
|
||||||
|
VoidT3: 0,
|
||||||
|
VoidT4: 0,
|
||||||
|
VoidT5: 0,
|
||||||
|
VoidT6: 0,
|
||||||
|
VoidT1Hard: 0,
|
||||||
|
VoidT2Hard: 0,
|
||||||
|
VoidT3Hard: 0,
|
||||||
|
VoidT4Hard: 0,
|
||||||
|
VoidT5Hard: 0,
|
||||||
|
VoidT6Hard: 0
|
||||||
|
};
|
||||||
|
for (const fissure of fissures) {
|
||||||
|
activeNodes.add(fissure.Node);
|
||||||
|
|
||||||
|
const key = fissure.Modifier + (fissure.Hard ? "Hard" : "");
|
||||||
|
tierToFurthestExpiry[key] = Math.max(tierToFurthestExpiry[key], fissure.Expiry.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
const deadline = Date.now() - 6 * unixTimesInMs.minute;
|
||||||
|
for (const [tier, expiry] of Object.entries(tierToFurthestExpiry)) {
|
||||||
|
if (expiry < deadline) {
|
||||||
|
const numFissures = getRandomInt(1, 3);
|
||||||
|
for (let i = 0; i != numFissures; ++i) {
|
||||||
|
const modifier = tier.replace("Hard", "") as
|
||||||
|
| "VoidT1"
|
||||||
|
| "VoidT2"
|
||||||
|
| "VoidT3"
|
||||||
|
| "VoidT4"
|
||||||
|
| "VoidT5"
|
||||||
|
| "VoidT6";
|
||||||
|
let node: string;
|
||||||
|
do {
|
||||||
|
node = getRandomElement(fissureMissions[modifier])!;
|
||||||
|
} while (activeNodes.has(node));
|
||||||
|
activeNodes.add(node);
|
||||||
|
await Fissure.insertOne({
|
||||||
|
Activation: new Date(),
|
||||||
|
Expiry: new Date(Date.now() + getRandomInt(60, 120) * unixTimesInMs.minute),
|
||||||
|
Node: node,
|
||||||
|
Modifier: modifier,
|
||||||
|
Hard: tier.indexOf("Hard") != -1 ? true : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDailyDeal = async (): Promise<void> => {
|
||||||
|
let darvoIndex = Math.trunc((Date.now() - 25200000) / (26 * unixTimesInMs.hour));
|
||||||
|
let darvoEnd;
|
||||||
|
do {
|
||||||
|
const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000;
|
||||||
|
darvoEnd = darvoStart + 26 * unixTimesInMs.hour;
|
||||||
|
const darvoOid = ((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95";
|
||||||
|
if (!(await DailyDeal.findById(darvoOid))) {
|
||||||
|
const seed = new SRng(darvoIndex).randomInt(0, 100_000);
|
||||||
|
const rng = new SRng(seed);
|
||||||
|
let deal;
|
||||||
|
do {
|
||||||
|
deal = rng.randomReward(darvoDeals)!; // Using an actual sampling collected over roughly a year because I can't extrapolate an algorithm from it with enough certainty.
|
||||||
|
//const [storeItem, meta] = rng.randomElement(Object.entries(darvoDeals))!;
|
||||||
|
//const discount = Math.min(rng.randomInt(1, 9) * 10, (meta as { MaxDiscount?: number }).MaxDiscount ?? 1);
|
||||||
|
} while (await DailyDeal.exists({ StoreItem: deal.StoreItem }));
|
||||||
|
await DailyDeal.insertOne({
|
||||||
|
_id: darvoOid,
|
||||||
|
StoreItem: deal.StoreItem,
|
||||||
|
Activation: new Date(darvoStart),
|
||||||
|
Expiry: new Date(darvoEnd),
|
||||||
|
Discount: deal.Discount,
|
||||||
|
OriginalPrice: deal.OriginalPrice,
|
||||||
|
SalePrice: deal.SalePrice, //Math.trunc(deal.OriginalPrice * (1 - discount))
|
||||||
|
AmountTotal: deal.AmountTotal,
|
||||||
|
AmountSold: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} while (darvoEnd < Date.now() + 6 * unixTimesInMs.minute && ++darvoIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateWorldStateCollections = async (): Promise<void> => {
|
||||||
|
await Promise.all([updateFissures(), updateDailyDeal()]);
|
||||||
|
};
|
||||||
|
@ -40,6 +40,7 @@ export interface IInventoryDatabase
|
|||||||
| "InfestedFoundry"
|
| "InfestedFoundry"
|
||||||
| "DialogueHistory"
|
| "DialogueHistory"
|
||||||
| "KubrowPetEggs"
|
| "KubrowPetEggs"
|
||||||
|
| "KubrowPetPrints"
|
||||||
| "PendingCoupon"
|
| "PendingCoupon"
|
||||||
| "Drones"
|
| "Drones"
|
||||||
| "RecentVendorPurchases"
|
| "RecentVendorPurchases"
|
||||||
@ -56,6 +57,7 @@ export interface IInventoryDatabase
|
|||||||
| "QualifyingInvasions"
|
| "QualifyingInvasions"
|
||||||
| "LastInventorySync"
|
| "LastInventorySync"
|
||||||
| "EndlessXP"
|
| "EndlessXP"
|
||||||
|
| "PersonalGoalProgress"
|
||||||
| TEquipmentKey
|
| TEquipmentKey
|
||||||
>,
|
>,
|
||||||
InventoryDatabaseEquipment {
|
InventoryDatabaseEquipment {
|
||||||
@ -63,7 +65,7 @@ export interface IInventoryDatabase
|
|||||||
Created: Date;
|
Created: Date;
|
||||||
TrainingDate: Date;
|
TrainingDate: Date;
|
||||||
LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
|
LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
|
||||||
Mailbox?: IMailboxDatabase;
|
//Mailbox?: IMailboxDatabase;
|
||||||
GuildId?: Types.ObjectId;
|
GuildId?: Types.ObjectId;
|
||||||
PendingRecipes: IPendingRecipeDatabase[];
|
PendingRecipes: IPendingRecipeDatabase[];
|
||||||
QuestKeys: IQuestKeyDatabase[];
|
QuestKeys: IQuestKeyDatabase[];
|
||||||
@ -78,7 +80,8 @@ export interface IInventoryDatabase
|
|||||||
KahlLoadOuts: IOperatorConfigDatabase[];
|
KahlLoadOuts: IOperatorConfigDatabase[];
|
||||||
InfestedFoundry?: IInfestedFoundryDatabase;
|
InfestedFoundry?: IInfestedFoundryDatabase;
|
||||||
DialogueHistory?: IDialogueHistoryDatabase;
|
DialogueHistory?: IDialogueHistoryDatabase;
|
||||||
KubrowPetEggs?: IKubrowPetEggDatabase[];
|
KubrowPetEggs: IKubrowPetEggDatabase[];
|
||||||
|
KubrowPetPrints: IKubrowPetPrintDatabase[];
|
||||||
PendingCoupon?: IPendingCouponDatabase;
|
PendingCoupon?: IPendingCouponDatabase;
|
||||||
Drones: IDroneDatabase[];
|
Drones: IDroneDatabase[];
|
||||||
RecentVendorPurchases?: IRecentVendorPurchaseDatabase[];
|
RecentVendorPurchases?: IRecentVendorPurchaseDatabase[];
|
||||||
@ -95,6 +98,7 @@ export interface IInventoryDatabase
|
|||||||
QualifyingInvasions: IInvasionProgressDatabase[];
|
QualifyingInvasions: IInvasionProgressDatabase[];
|
||||||
LastInventorySync?: Types.ObjectId;
|
LastInventorySync?: Types.ObjectId;
|
||||||
EndlessXP?: IEndlessXpProgressDatabase[];
|
EndlessXP?: IEndlessXpProgressDatabase[];
|
||||||
|
PersonalGoalProgress?: IPersonalGoalProgressDatabase[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuestKeyDatabase {
|
export interface IQuestKeyDatabase {
|
||||||
@ -150,9 +154,9 @@ export interface IMailboxClient {
|
|||||||
LastInboxId: IOid;
|
LastInboxId: IOid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMailboxDatabase {
|
/*export interface IMailboxDatabase {
|
||||||
LastInboxId: Types.ObjectId;
|
LastInboxId: Types.ObjectId;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
export type TSolarMapRegion =
|
export type TSolarMapRegion =
|
||||||
| "Earth"
|
| "Earth"
|
||||||
@ -234,7 +238,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
HandlerPoints: number;
|
HandlerPoints: number;
|
||||||
MiscItems: IMiscItem[];
|
MiscItems: IMiscItem[];
|
||||||
HasOwnedVoidProjectionsPreviously?: boolean;
|
HasOwnedVoidProjectionsPreviously?: boolean;
|
||||||
ChallengesFixVersion: number;
|
ChallengesFixVersion?: number;
|
||||||
ChallengeProgress: IChallengeProgress[];
|
ChallengeProgress: IChallengeProgress[];
|
||||||
RawUpgrades: IRawUpgrade[];
|
RawUpgrades: IRawUpgrade[];
|
||||||
ReceivedStartingGear: boolean;
|
ReceivedStartingGear: boolean;
|
||||||
@ -285,6 +289,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
ArchwingEnabled?: boolean;
|
ArchwingEnabled?: boolean;
|
||||||
PendingSpectreLoadouts?: ISpectreLoadout[];
|
PendingSpectreLoadouts?: ISpectreLoadout[];
|
||||||
SpectreLoadouts?: ISpectreLoadout[];
|
SpectreLoadouts?: ISpectreLoadout[];
|
||||||
|
UsedDailyDeals: string[];
|
||||||
EmailItems: ITypeCount[];
|
EmailItems: ITypeCount[];
|
||||||
CompletedSyndicates: string[];
|
CompletedSyndicates: string[];
|
||||||
FocusXP?: IFocusXP;
|
FocusXP?: IFocusXP;
|
||||||
@ -293,7 +298,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
CompletedSorties: string[];
|
CompletedSorties: string[];
|
||||||
LastSortieReward?: ILastSortieRewardClient[];
|
LastSortieReward?: ILastSortieRewardClient[];
|
||||||
LastLiteSortieReward?: ILastSortieRewardClient[];
|
LastLiteSortieReward?: ILastSortieRewardClient[];
|
||||||
SortieRewardAttenuation?: ISortieRewardAttenuation[];
|
SortieRewardAttenuation?: IRewardAttenuation[];
|
||||||
Drones: IDroneClient[];
|
Drones: IDroneClient[];
|
||||||
StepSequencers: IStepSequencer[];
|
StepSequencers: IStepSequencer[];
|
||||||
ActiveAvatarImageType?: string;
|
ActiveAvatarImageType?: string;
|
||||||
@ -304,9 +309,9 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
FocusUpgrades: IFocusUpgrade[];
|
FocusUpgrades: IFocusUpgrade[];
|
||||||
HasContributedToDojo?: boolean;
|
HasContributedToDojo?: boolean;
|
||||||
HWIDProtectEnabled?: boolean;
|
HWIDProtectEnabled?: boolean;
|
||||||
//KubrowPetPrints: IKubrowPetPrint[];
|
KubrowPetPrints: IKubrowPetPrintClient[];
|
||||||
AlignmentReplay?: IAlignment;
|
AlignmentReplay?: IAlignment;
|
||||||
//PersonalGoalProgress: IPersonalGoalProgress[];
|
PersonalGoalProgress?: IPersonalGoalProgressClient[];
|
||||||
ThemeStyle: string;
|
ThemeStyle: string;
|
||||||
ThemeBackground: string;
|
ThemeBackground: string;
|
||||||
ThemeSounds: string;
|
ThemeSounds: string;
|
||||||
@ -349,7 +354,6 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
//LeagueTickets: any[];
|
//LeagueTickets: any[];
|
||||||
//Quests: any[];
|
//Quests: any[];
|
||||||
//Robotics: any[];
|
//Robotics: any[];
|
||||||
//UsedDailyDeals: any[];
|
|
||||||
LibraryPersonalTarget?: string;
|
LibraryPersonalTarget?: string;
|
||||||
LibraryPersonalProgress: ILibraryPersonalProgress[];
|
LibraryPersonalProgress: ILibraryPersonalProgress[];
|
||||||
CollectibleSeries?: ICollectibleEntry[];
|
CollectibleSeries?: ICollectibleEntry[];
|
||||||
@ -378,6 +382,8 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
LockedWeaponGroup?: ILockedWeaponGroupClient;
|
LockedWeaponGroup?: ILockedWeaponGroupClient;
|
||||||
HubNpcCustomizations?: IHubNpcCustomization[];
|
HubNpcCustomizations?: IHubNpcCustomization[];
|
||||||
Ship?: IOrbiter; // U22 and below, response only
|
Ship?: IOrbiter; // U22 and below, response only
|
||||||
|
ClaimedJunctionChallengeRewards?: string[]; // U39
|
||||||
|
SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAffiliation {
|
export interface IAffiliation {
|
||||||
@ -446,8 +452,9 @@ export interface IVendorPurchaseHistoryEntryDatabase {
|
|||||||
|
|
||||||
export interface IChallengeProgress {
|
export interface IChallengeProgress {
|
||||||
Progress: number;
|
Progress: number;
|
||||||
Name: string;
|
|
||||||
Completed?: string[];
|
Completed?: string[];
|
||||||
|
ReceivedJunctionReward?: boolean; // U39
|
||||||
|
Name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICollectibleEntry {
|
export interface ICollectibleEntry {
|
||||||
@ -641,11 +648,11 @@ export interface IFocusUpgrade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IFocusXP {
|
export interface IFocusXP {
|
||||||
AP_POWER: number;
|
AP_POWER?: number;
|
||||||
AP_TACTIC: number;
|
AP_TACTIC?: number;
|
||||||
AP_DEFENSE: number;
|
AP_DEFENSE?: number;
|
||||||
AP_ATTACK: number;
|
AP_ATTACK?: number;
|
||||||
AP_WARD: number;
|
AP_WARD?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TFocusPolarity = keyof IFocusXP;
|
export type TFocusPolarity = keyof IFocusXP;
|
||||||
@ -718,8 +725,8 @@ export interface IKubrowPetEggDatabase {
|
|||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IKubrowPetPrint {
|
export interface IKubrowPetPrintClient {
|
||||||
ItemType: KubrowPetPrintItemType;
|
ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint";
|
||||||
Name: string;
|
Name: string;
|
||||||
IsMale: boolean;
|
IsMale: boolean;
|
||||||
Size: number; // seems to be 0.7 to 1.0
|
Size: number; // seems to be 0.7 to 1.0
|
||||||
@ -729,6 +736,10 @@ export interface IKubrowPetPrint {
|
|||||||
InheritedModularParts?: any[];
|
InheritedModularParts?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IKubrowPetPrintDatabase extends Omit<IKubrowPetPrintClient, "ItemId" | "InheritedModularParts"> {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ITraits {
|
export interface ITraits {
|
||||||
BaseColor: string;
|
BaseColor: string;
|
||||||
SecondaryColor: string;
|
SecondaryColor: string;
|
||||||
@ -742,15 +753,11 @@ export interface ITraits {
|
|||||||
Tail?: string;
|
Tail?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum KubrowPetPrintItemType {
|
|
||||||
LotusTypesGameKubrowPetImprintedTraitPrint = "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IKubrowPetDetailsDatabase {
|
export interface IKubrowPetDetailsDatabase {
|
||||||
Name?: string;
|
Name?: string;
|
||||||
IsPuppy?: boolean;
|
IsPuppy?: boolean;
|
||||||
HasCollar: boolean;
|
HasCollar: boolean;
|
||||||
PrintsRemaining?: number;
|
PrintsRemaining: number;
|
||||||
Status: Status;
|
Status: Status;
|
||||||
HatchDate?: Date;
|
HatchDate?: Date;
|
||||||
DominantTraits: ITraits;
|
DominantTraits: ITraits;
|
||||||
@ -779,7 +786,7 @@ export interface ILastSortieRewardDatabase extends Omit<ILastSortieRewardClient,
|
|||||||
SortieId: Types.ObjectId;
|
SortieId: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISortieRewardAttenuation {
|
export interface IRewardAttenuation {
|
||||||
Tag: string;
|
Tag: string;
|
||||||
Atten: number;
|
Atten: number;
|
||||||
}
|
}
|
||||||
@ -1015,13 +1022,17 @@ export interface IPeriodicMissionCompletionResponse extends Omit<IPeriodicMissio
|
|||||||
date: IMongoDate;
|
date: IMongoDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPersonalGoalProgress {
|
export interface IPersonalGoalProgressClient {
|
||||||
|
Best: number;
|
||||||
Count: number;
|
Count: number;
|
||||||
Tag: string;
|
Tag: string;
|
||||||
Best?: number;
|
|
||||||
_id: IOid;
|
_id: IOid;
|
||||||
ReceivedClanReward0?: boolean;
|
//ReceivedClanReward0?: boolean;
|
||||||
ReceivedClanReward1?: boolean;
|
//ReceivedClanReward1?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPersonalGoalProgressDatabase extends Omit<IPersonalGoalProgressClient, "_id"> {
|
||||||
|
goalId: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPersonalTechProjectDatabase {
|
export interface IPersonalTechProjectDatabase {
|
||||||
|
@ -2,7 +2,7 @@ import { Types } from "mongoose";
|
|||||||
|
|
||||||
export interface IAccountAndLoginResponseCommons {
|
export interface IAccountAndLoginResponseCommons {
|
||||||
DisplayName: string;
|
DisplayName: string;
|
||||||
CountryCode: string;
|
CountryCode?: string;
|
||||||
ClientType?: string;
|
ClientType?: string;
|
||||||
CrossPlatformAllowed?: boolean;
|
CrossPlatformAllowed?: boolean;
|
||||||
ForceLogoutVersion?: number;
|
ForceLogoutVersion?: number;
|
||||||
@ -25,6 +25,7 @@ export interface IDatabaseAccount extends IDatabaseAccountRequiredFields {
|
|||||||
LatestEventMessageDate: Date;
|
LatestEventMessageDate: Date;
|
||||||
LastLoginRewardDate: number;
|
LastLoginRewardDate: number;
|
||||||
LoginDays: number;
|
LoginDays: number;
|
||||||
|
DailyFirstWinDate: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Includes virtual ID
|
// Includes virtual ID
|
||||||
|
@ -17,9 +17,9 @@ export interface IMissionReward {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IMissionCredits {
|
export interface IMissionCredits {
|
||||||
MissionCredits: number[];
|
MissionCredits: [number, number];
|
||||||
CreditBonus: number[];
|
CreditsBonus: [number, number]; // "Credit Reward"; `CreditsBonus[1]` is `CreditsBonus[0] * 2` if DailyMissionBonus
|
||||||
TotalCredits: number[];
|
TotalCredits: [number, number];
|
||||||
DailyMissionBonus?: boolean;
|
DailyMissionBonus?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,17 +7,46 @@ import {
|
|||||||
ITypeCount,
|
ITypeCount,
|
||||||
IRecentVendorPurchaseClient,
|
IRecentVendorPurchaseClient,
|
||||||
TEquipmentKey,
|
TEquipmentKey,
|
||||||
ICrewMemberClient
|
ICrewMemberClient,
|
||||||
|
IKubrowPetPrintClient
|
||||||
} from "./inventoryTypes/inventoryTypes";
|
} from "./inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
|
export enum PurchaseSource {
|
||||||
|
Market = 0,
|
||||||
|
VoidTrader = 1,
|
||||||
|
SyndicateFavor = 2,
|
||||||
|
DailyDeal = 3,
|
||||||
|
Arsenal = 4,
|
||||||
|
Profile = 5,
|
||||||
|
Hub = 6,
|
||||||
|
Vendor = 7,
|
||||||
|
AppearancePreview = 8,
|
||||||
|
Museum = 9,
|
||||||
|
Operator = 10,
|
||||||
|
PlayerShip = 11,
|
||||||
|
Crewship = 12,
|
||||||
|
MenuStyle = 13,
|
||||||
|
MenuHud = 14,
|
||||||
|
Chat = 15,
|
||||||
|
Inventory = 16,
|
||||||
|
StarChart = 17,
|
||||||
|
PrimeVaultTrader = 18,
|
||||||
|
Incubator = 19,
|
||||||
|
Prompt = 20,
|
||||||
|
Kaithe = 21,
|
||||||
|
DuviriWeapon = 22,
|
||||||
|
UpdateScreen = 23,
|
||||||
|
Motorcycle = 24
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPurchaseRequest {
|
export interface IPurchaseRequest {
|
||||||
PurchaseParams: IPurchaseParams;
|
PurchaseParams: IPurchaseParams;
|
||||||
buildLabel: string;
|
buildLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPurchaseParams {
|
export interface IPurchaseParams {
|
||||||
Source: number;
|
Source: PurchaseSource;
|
||||||
SourceId?: string; // for Source 1, 7 & 18
|
SourceId?: string; // VoidTrader, Vendor, PrimeVaultTrader
|
||||||
StoreItem: string;
|
StoreItem: string;
|
||||||
StorePage: string;
|
StorePage: string;
|
||||||
SearchTerm: string;
|
SearchTerm: string;
|
||||||
@ -25,10 +54,10 @@ export interface IPurchaseParams {
|
|||||||
Quantity: number;
|
Quantity: number;
|
||||||
UsePremium: boolean;
|
UsePremium: boolean;
|
||||||
ExpectedPrice: number;
|
ExpectedPrice: number;
|
||||||
SyndicateTag?: string; // for Source 2
|
SyndicateTag?: string; // SyndicateFavor
|
||||||
UseFreeFavor?: boolean; // for Source 2
|
UseFreeFavor?: boolean; // SyndicateFavor
|
||||||
ExtraPurchaseInfoJson?: string; // for Source 7
|
ExtraPurchaseInfoJson?: string; // Vendor
|
||||||
IsWeekly?: boolean; // for Source 7
|
IsWeekly?: boolean; // Vendor
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IInventoryChanges = {
|
export type IInventoryChanges = {
|
||||||
@ -50,6 +79,7 @@ export type IInventoryChanges = {
|
|||||||
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
|
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
|
||||||
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
|
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
|
||||||
CrewMembers?: ICrewMemberClient[];
|
CrewMembers?: ICrewMemberClient[];
|
||||||
|
KubrowPetPrints?: IKubrowPetPrintClient[];
|
||||||
} & Record<
|
} & Record<
|
||||||
Exclude<
|
Exclude<
|
||||||
string,
|
string,
|
||||||
@ -77,6 +107,7 @@ export interface IPurchaseResponse {
|
|||||||
Standing?: IAffiliationMods[];
|
Standing?: IAffiliationMods[];
|
||||||
FreeFavorsUsed?: IAffiliationMods[];
|
FreeFavorsUsed?: IAffiliationMods[];
|
||||||
BoosterPackItems?: string;
|
BoosterPackItems?: string;
|
||||||
|
DailyDealUsed?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IBinChanges = {
|
export type IBinChanges = {
|
||||||
|
@ -139,6 +139,14 @@ export type IMissionInventoryUpdateRequest = {
|
|||||||
};
|
};
|
||||||
wagerTier?: number; // the index
|
wagerTier?: number; // the index
|
||||||
creditsFee?: number; // the index
|
creditsFee?: number; // the index
|
||||||
|
GoalProgress?: {
|
||||||
|
_id: IOid;
|
||||||
|
Count: number;
|
||||||
|
Best: number;
|
||||||
|
Tag: string;
|
||||||
|
IsMultiProgress: boolean;
|
||||||
|
MultiProgress: unknown[];
|
||||||
|
}[];
|
||||||
InvasionProgress?: IInvasionProgressClient[];
|
InvasionProgress?: IInvasionProgressClient[];
|
||||||
ConquestMissionsCompleted?: number;
|
ConquestMissionsCompleted?: number;
|
||||||
duviriSuitSelection?: string;
|
duviriSuitSelection?: string;
|
||||||
@ -156,6 +164,8 @@ export type IMissionInventoryUpdateRequest = {
|
|||||||
|
|
||||||
export interface IRewardInfo {
|
export interface IRewardInfo {
|
||||||
node: string;
|
node: string;
|
||||||
|
goalId?: string;
|
||||||
|
goalManifest?: string;
|
||||||
invasionId?: string;
|
invasionId?: string;
|
||||||
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
|
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
|
||||||
sortieId?: string;
|
sortieId?: string;
|
||||||
|
@ -96,6 +96,7 @@ export interface IPlacedDecosDatabase {
|
|||||||
Pos: [number, number, number];
|
Pos: [number, number, number];
|
||||||
Rot: [number, number, number];
|
Rot: [number, number, number];
|
||||||
Scale?: number;
|
Scale?: number;
|
||||||
|
Sockets?: number;
|
||||||
PictureFrameInfo?: IPictureFrameInfo;
|
PictureFrameInfo?: IPictureFrameInfo;
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
}
|
}
|
||||||
@ -136,6 +137,7 @@ export interface IShipDecorationsRequest {
|
|||||||
MoveId?: string;
|
MoveId?: string;
|
||||||
OldRoom?: string;
|
OldRoom?: string;
|
||||||
Scale?: number;
|
Scale?: number;
|
||||||
|
Sockets?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IShipDecorationsResponse {
|
export interface IShipDecorationsResponse {
|
||||||
@ -152,7 +154,7 @@ export interface ISetPlacedDecoInfoRequest {
|
|||||||
DecoId: string;
|
DecoId: string;
|
||||||
Room: string;
|
Room: string;
|
||||||
PictureFrameInfo: IPictureFrameInfo;
|
PictureFrameInfo: IPictureFrameInfo;
|
||||||
BootLocation?: string;
|
BootLocation?: TBootLocation;
|
||||||
ComponentId?: string;
|
ComponentId?: string;
|
||||||
GuildId?: string;
|
GuildId?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { IMissionReward } from "warframe-public-export-plus";
|
||||||
import { IMongoDate, IOid } from "./commonTypes";
|
import { IMongoDate, IOid } from "./commonTypes";
|
||||||
|
|
||||||
export interface IWorldState {
|
export interface IWorldState {
|
||||||
@ -9,9 +10,12 @@ export interface IWorldState {
|
|||||||
Sorties: ISortie[];
|
Sorties: ISortie[];
|
||||||
LiteSorties: ILiteSortie[];
|
LiteSorties: ILiteSortie[];
|
||||||
SyndicateMissions: ISyndicateMissionInfo[];
|
SyndicateMissions: ISyndicateMissionInfo[];
|
||||||
GlobalUpgrades: IGlobalUpgrade[];
|
|
||||||
ActiveMissions: IFissure[];
|
ActiveMissions: IFissure[];
|
||||||
|
GlobalUpgrades: IGlobalUpgrade[];
|
||||||
NodeOverrides: INodeOverride[];
|
NodeOverrides: INodeOverride[];
|
||||||
|
VoidTraders: IVoidTrader[];
|
||||||
|
VoidStorms: IVoidStorm[];
|
||||||
|
DailyDeals: IDailyDeal[];
|
||||||
PVPChallengeInstances: IPVPChallengeInstance[];
|
PVPChallengeInstances: IPVPChallengeInstance[];
|
||||||
EndlessXpChoices: IEndlessXpChoice[];
|
EndlessXpChoices: IEndlessXpChoice[];
|
||||||
SeasonInfo?: {
|
SeasonInfo?: {
|
||||||
@ -35,11 +39,15 @@ export interface IGoal {
|
|||||||
Goal: number;
|
Goal: number;
|
||||||
Success: number;
|
Success: number;
|
||||||
Personal: boolean;
|
Personal: boolean;
|
||||||
|
Bounty?: boolean;
|
||||||
|
ClampNodeScores?: boolean;
|
||||||
Desc: string;
|
Desc: string;
|
||||||
ToolTip: string;
|
ToolTip?: string;
|
||||||
Icon: string;
|
Icon: string;
|
||||||
Tag: string;
|
Tag: string;
|
||||||
Node: string;
|
Node: string;
|
||||||
|
MissionKeyName?: string;
|
||||||
|
Reward?: IMissionReward;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISyndicateMissionInfo {
|
export interface ISyndicateMissionInfo {
|
||||||
@ -85,6 +93,14 @@ export interface IFissure {
|
|||||||
Hard?: boolean;
|
Hard?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFissureDatabase {
|
||||||
|
Activation: Date;
|
||||||
|
Expiry: Date;
|
||||||
|
Node: string;
|
||||||
|
Modifier: "VoidT1" | "VoidT2" | "VoidT3" | "VoidT4" | "VoidT5" | "VoidT6";
|
||||||
|
Hard?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface INodeOverride {
|
export interface INodeOverride {
|
||||||
_id: IOid;
|
_id: IOid;
|
||||||
Activation?: IMongoDate;
|
Activation?: IMongoDate;
|
||||||
@ -131,6 +147,52 @@ export interface ILiteSortie {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IVoidTrader {
|
||||||
|
_id: IOid;
|
||||||
|
Activation: IMongoDate;
|
||||||
|
Expiry: IMongoDate;
|
||||||
|
Character: string;
|
||||||
|
Node: string;
|
||||||
|
Manifest: IVoidTraderOffer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVoidTraderOffer {
|
||||||
|
ItemType: string;
|
||||||
|
PrimePrice: number;
|
||||||
|
RegularPrice: number;
|
||||||
|
Limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVoidStorm {
|
||||||
|
_id: IOid;
|
||||||
|
Node: string;
|
||||||
|
Activation: IMongoDate;
|
||||||
|
Expiry: IMongoDate;
|
||||||
|
ActiveMissionTier: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDailyDeal {
|
||||||
|
StoreItem: string;
|
||||||
|
Activation: IMongoDate;
|
||||||
|
Expiry: IMongoDate;
|
||||||
|
Discount: number;
|
||||||
|
OriginalPrice: number;
|
||||||
|
SalePrice: number;
|
||||||
|
AmountTotal: number;
|
||||||
|
AmountSold: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDailyDealDatabase {
|
||||||
|
StoreItem: string;
|
||||||
|
Activation: Date;
|
||||||
|
Expiry: Date;
|
||||||
|
Discount: number;
|
||||||
|
OriginalPrice: number;
|
||||||
|
SalePrice: number;
|
||||||
|
AmountTotal: number;
|
||||||
|
AmountSold: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPVPChallengeInstance {
|
export interface IPVPChallengeInstance {
|
||||||
_id: IOid;
|
_id: IOid;
|
||||||
challengeTypeRefID: string;
|
challengeTypeRefID: string;
|
||||||
@ -182,3 +244,48 @@ export interface ICalendarEvent {
|
|||||||
dialogueName?: string;
|
dialogueName?: string;
|
||||||
dialogueConvo?: string;
|
dialogueConvo?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TCircuitGameMode =
|
||||||
|
| "Survival"
|
||||||
|
| "VoidFlood"
|
||||||
|
| "Excavation"
|
||||||
|
| "Defense"
|
||||||
|
| "Exterminate"
|
||||||
|
| "Assassination"
|
||||||
|
| "Alchemy";
|
||||||
|
|
||||||
|
export interface ITmp {
|
||||||
|
cavabegin: string;
|
||||||
|
PurchasePlatformLockEnabled: boolean; // Seems unused
|
||||||
|
pgr: IPgr;
|
||||||
|
ennnd?: boolean; // True if 1999 demo is available (no effect for >=38.6.0)
|
||||||
|
mbrt?: boolean; // Related to mobile app rating request
|
||||||
|
fbst: IFbst;
|
||||||
|
sfn: number;
|
||||||
|
edg?: TCircuitGameMode[]; // The Circuit game modes overwrite
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPgr {
|
||||||
|
ts: string;
|
||||||
|
en: string;
|
||||||
|
fr: string;
|
||||||
|
it: string;
|
||||||
|
de: string;
|
||||||
|
es: string;
|
||||||
|
pt: string;
|
||||||
|
ru: string;
|
||||||
|
pl: string;
|
||||||
|
uk: string;
|
||||||
|
tr: string;
|
||||||
|
ja: string;
|
||||||
|
zh: string;
|
||||||
|
ko: string;
|
||||||
|
tc: string;
|
||||||
|
th: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFbst {
|
||||||
|
a: number;
|
||||||
|
e: number;
|
||||||
|
n: number;
|
||||||
|
}
|
||||||
|
@ -20,6 +20,10 @@
|
|||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyGoldARecipe",
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyGoldARecipe",
|
||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyPlatinumARecipe",
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyPlatinumARecipe",
|
||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophySilverARecipe",
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophySilverARecipe",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventBronzeTrophyRecipe",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventClayTrophyRecipe",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventGoldTrophyRecipe",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventSilverTrophyRecipe",
|
||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBaseTrophyRecipe",
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBaseTrophyRecipe",
|
||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBronzeTrophyRecipe",
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBronzeTrophyRecipe",
|
||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventGoldTrophyRecipe",
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventGoldTrophyRecipe",
|
||||||
@ -88,5 +92,13 @@
|
|||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophySilverRecipe",
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophySilverRecipe",
|
||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/CorpusPlaceables/GasTurbineConeRecipe",
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/CorpusPlaceables/GasTurbineConeRecipe",
|
||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe",
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe",
|
||||||
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoPlaceables/TnoBeaconEmitterRecipe"
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoPlaceables/TnoBeaconEmitterRecipe",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronFemaleSitting",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronFemaleStanding",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronMaleStanding",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronMaleStandingTwo",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisForeman",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisHazard",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisStrikerOne",
|
||||||
|
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisStrikerThree"
|
||||||
]
|
]
|
||||||
|
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