From bf40155dd4c7979df4d92084fc05fcf752d0f48e Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 04:22:54 -0700 Subject: [PATCH 01/16] chore: no-op nemesis mode=w (#2196) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2196 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/nemesisController.ts | 25 +++++------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 56cc5562..93ea4ee5 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -151,7 +151,8 @@ export const nemesisController: RequestHandler = async (req, res) => { inventory.Nemesis!.HenchmenKilled += antivirusGain; if (inventory.Nemesis!.HenchmenKilled >= 100) { inventory.Nemesis!.HenchmenKilled = 100; - // Client doesn't seem to request mode=w for infested liches, so weakening it here. + + // Weaken nemesis now. inventory.Nemesis!.InfNodes = [ { Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode, @@ -294,31 +295,15 @@ export const nemesisController: RequestHandler = async (req, res) => { target: inventory.toJSON().Nemesis }); } else if ((req.query.mode as string) == "w") { - const inventory = await getInventory( - account._id.toString(), - "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades" - ); + const inventory = await getInventory(account._id.toString(), "Nemesis"); //const body = getJSONfromString(String(req.body)); - if (inventory.Nemesis!.Weakened) { - logger.warn(`client is weakening an already-weakened nemesis?!`); - } - - inventory.Nemesis!.InfNodes = [ - { - Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode, - Influence: 1 - } - ]; - inventory.Nemesis!.Weakened = true; + // 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. const response: INemesisWeakenResponse = { target: inventory.toJSON().Nemesis! }; - - await consumePasscodeModCharges(inventory, response); - - await inventory.save(); res.json(response); } else { logger.debug(`data provided to ${req.path}: ${String(req.body)}`); From 4ca4990f89619d32aa848cf67ab6fd858e76a67d Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 04:23:10 -0700 Subject: [PATCH 02/16] chore(docker): use file-based config & precompile code in image (#2202) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2202 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- Dockerfile | 50 +++--------------------------------- docker-compose.yml | 61 +++++++------------------------------------- docker-entrypoint.sh | 24 +++-------------- 3 files changed, 17 insertions(+), 118 deletions(-) diff --git a/Dockerfile b/Dockerfile index b5b957e7..ec346634 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,53 +1,11 @@ FROM node:24-alpine3.21 -ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF -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 +RUN apk add --no-cache bash jq COPY . /app WORKDIR /app +RUN npm i --omit=dev +RUN npm run build + ENTRYPOINT ["/app/docker-entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index 544dec95..aa328756 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,62 +1,18 @@ services: spaceninjaserver: - # build: . - 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: - - ./docker-data/static:/app/static/data + - ./docker-data/conf:/app/conf + - ./docker-data/static-data:/app/static/data - ./docker-data/logs:/app/logs ports: - 80:80 - 443:443 + + # Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by swapping the following two fields. + # Works best when using `docker-compose up --force-recreate --build`. + image: openwf/spaceninjaserver:latest + #build: . + depends_on: - mongodb mongodb: @@ -66,3 +22,4 @@ services: MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver volumes: - ./docker-data/database:/data/db + command: mongod --quiet --logpath /dev/null diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 13e70c33..457173d9 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,24 +1,8 @@ #!/bin/bash set -e -# Set up the configuration file using environment variables. -echo '{ - "logger": { - "files": true, - "level": "trace", - "__valid_levels": "fatal, error, warn, info, http, debug, trace" - } -} -' > config.json +if [ ! -f conf/config.json ]; then + jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config.json.example > /app/conf/config.json +fi -for config in $(env | grep "APP_") -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 +exec npm run start conf/config.json From d78ca91d6c60c748f8d883b7d59c7f3d9b611072 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 04:23:33 -0700 Subject: [PATCH 03/16] fix(webui): properly handle renaming of pets (#2204) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2204 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/renamePetController.ts | 11 +++++-- static/webui/script.js | 34 ++++++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/controllers/api/renamePetController.ts b/src/controllers/api/renamePetController.ts index 6672d064..61212641 100644 --- a/src/controllers/api/renamePetController.ts +++ b/src/controllers/api/renamePetController.ts @@ -1,6 +1,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { RequestHandler } from "express"; export const renamePetController: RequestHandler = async (req, res) => { @@ -8,12 +9,18 @@ export const renamePetController: RequestHandler = async (req, res) => { const inventory = await getInventory(accountId, "KubrowPets PremiumCredits PremiumCreditsFree"); const data = getJSONfromString(String(req.body)); const details = inventory.KubrowPets.id(data.petId)!.Details!; + 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(); res.json({ ...data, - inventoryChanges: currencyChanges + inventoryChanges: inventoryChanges }); }; diff --git a/static/webui/script.js b/static/webui/script.js index bb7ec52a..bd32266f 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -546,6 +546,9 @@ function updateInventory() { td.textContent = item.ItemName + " (" + td.textContent + ")"; } } + if (item.Details?.Name) { + td.textContent = item.Details.Name + " (" + td.textContent + ")"; + } if (item.ModularParts && item.ModularParts.length) { td.textContent += " ["; item.ModularParts.forEach(part => { @@ -1506,15 +1509,28 @@ function sendBatchGearExp(data) { function renameGear(category, oid, name) { revalidateAuthz(() => { - $.post({ - url: "/api/nameWeapon.php?" + window.authz + "&Category=" + category + "&ItemId=" + oid + "&webui=1", - contentType: "text/plain", - data: JSON.stringify({ - ItemName: name - }) - }).done(function () { - updateInventory(); - }); + if (category == "KubrowPets") { + $.post({ + url: "/api/renamePet.php?" + window.authz + "&webui=1", + contentType: "text/plain", + data: JSON.stringify({ + petId: oid, + name: name + }) + }).done(function () { + updateInventory(); + }); + } else { + $.post({ + url: "/api/nameWeapon.php?" + window.authz + "&Category=" + category + "&ItemId=" + oid + "&webui=1", + contentType: "text/plain", + data: JSON.stringify({ + ItemName: name + }) + }).done(function () { + updateInventory(); + }); + } }); } From 61a8d01f64b1f8a9811666d14caeef20324eeb76 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 13:47:04 +0200 Subject: [PATCH 04/16] ci: split docker amd64 & arm64 builds --- .github/workflows/docker.yml | 25 ++++++++++++++++++++++--- docker-compose.yml | 8 +++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 55626376..4a97729c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,9 +4,9 @@ on: branches: - main jobs: - docker: + docker-amd64: if: github.repository == 'OpenWF/SpaceNinjaServer' - runs-on: ubuntu-latest + runs-on: amd64 steps: - name: Set up Docker buildx uses: docker/setup-buildx-action@v3 @@ -18,8 +18,27 @@ jobs: - name: Build and push uses: docker/build-push-action@v6 with: - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64 push: true tags: | openwf/spaceninjaserver:latest 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 diff --git a/docker-compose.yml b/docker-compose.yml index aa328756..d9f89348 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,8 @@ services: spaceninjaserver: + # The image to use. If you have an ARM CPU, replace 'latest' with 'latest-arm64'. + image: openwf/spaceninjaserver:latest + volumes: - ./docker-data/conf:/app/conf - ./docker-data/static-data:/app/static/data @@ -8,10 +11,9 @@ services: - 80:80 - 443:443 - # Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by swapping the following two fields. - # Works best when using `docker-compose up --force-recreate --build`. - image: openwf/spaceninjaserver:latest + # 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: - mongodb From ad7b5fc05222202be24db36d5f7c7ecd029a4c46 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:15:27 -0700 Subject: [PATCH 05/16] fix(webui): incorrect description of topaz shields for blast (#2211) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2211 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/webui/translations/de.js | 2 +- static/webui/translations/en.js | 2 +- static/webui/translations/es.js | 2 +- static/webui/translations/fr.js | 2 +- static/webui/translations/ru.js | 2 +- static/webui/translations/zh.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index a4771cf3..c42d3cf9 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index f17c2e0a..aa35d2a9 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -184,7 +184,7 @@ dict = { upgrade_WarframeAbilityDuration: `+|VAL|% Ability Duration`, upgrade_WarframeAbilityStrength: `+|VAL|% Ability Strength`, upgrade_WarframeArmourMax: `+|VAL| Armor`, - upgrade_WarframeBlastProc: `+|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeBlastProc: `+|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `+|VAL|% Casting Speed`, upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Ability Damage on enemies affected by Corrosion Status`, upgrade_WarframeCorrosiveStack: `Increase max stacks of Corrosion Status by +|VAL|`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index c627dfa2..ad12b441 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `+|VAL|% de duración de habilidades`, upgrade_WarframeAbilityStrength: `+|VAL|% de fuerza de habilidades`, upgrade_WarframeArmourMax: `+|VAL| de armadura`, - upgrade_WarframeBlastProc: `+|VAL| de escudos al infligir estado de explosión`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `+|VAL|% de velocidad de lanzamiento de habilidades`, upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado corrosivo`, upgrade_WarframeCorrosiveStack: `Aumenta los acumuladores máximos de estado corrosivo en +|VAL|`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index e4c99dd6..e4fb0f3f 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index e0247b7e..e50f2959 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 22bc33f9..4b763583 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `+|VAL|% 技能持续时间`, upgrade_WarframeAbilityStrength: `+|VAL|% 技能强度`, upgrade_WarframeArmourMax: `+|VAL| 护甲`, - upgrade_WarframeBlastProc: `施加爆炸状态时,护盾 +|VAL|`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `+|VAL|% 施放速度`, upgrade_WarframeCorrosiveDamageBoost: `对受腐蚀状态影响的敌人 +|VAL|% 技能伤害`, upgrade_WarframeCorrosiveStack: `腐蚀状态最大堆叠数 +|VAL|`, From e136c0494e2c913b6973adb15943850d057d813c Mon Sep 17 00:00:00 2001 From: hxedcl Date: Thu, 19 Jun 2025 13:38:00 -0700 Subject: [PATCH 06/16] chore(webui): update to Spanish translation (#2213) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2213 Co-authored-by: hxedcl Co-committed-by: hxedcl --- static/webui/translations/es.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index ad12b441..d3eb257b 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `+|VAL|% de duración de habilidades`, upgrade_WarframeAbilityStrength: `+|VAL|% de fuerza de habilidades`, upgrade_WarframeArmourMax: `+|VAL| de armadura`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, + upgrade_WarframeBlastProc: `+|VAL| de escudos al matar con daño de explosión`, upgrade_WarframeCastingSpeed: `+|VAL|% de velocidad de lanzamiento de habilidades`, upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado corrosivo`, upgrade_WarframeCorrosiveStack: `Aumenta los acumuladores máximos de estado corrosivo en +|VAL|`, From 05382beaafbfd0c998922d8ef1fd1da29befa2eb Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:18:17 -0700 Subject: [PATCH 07/16] feat: worldState.duviriOverride config (#2206) Closes #2205 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2206 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- README.md | 1 + config.json.example | 1 + src/services/configService.ts | 1 + src/services/worldStateService.ts | 21 +++++++++++++++++++++ 4 files changed, 24 insertions(+) diff --git a/README.md b/README.md index dc57e796..0bd7de22 100644 --- a/README.md +++ b/README.md @@ -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 ]`. - `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.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: - `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9 - `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8 diff --git a/config.json.example b/config.json.example index 8f839751..4c5fafb5 100644 --- a/config.json.example +++ b/config.json.example @@ -58,6 +58,7 @@ "starDays": true, "eidolonOverride": "", "vallisOverride": "", + "duviriOverride": "", "nightwaveOverride": "", "circuitGameModes": null }, diff --git a/src/services/configService.ts b/src/services/configService.ts index 0c09eb23..a45645a2 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -64,6 +64,7 @@ export interface IConfig { starDays?: boolean; eidolonOverride?: string; vallisOverride?: string; + duviriOverride?: string; nightwaveOverride?: string; circuitGameModes?: string[]; }; diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 5f2df857..8fa82eed 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1068,6 +1068,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; }; From 88d4ba63405a289614bf61f090caa844156d3647 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:18:35 -0700 Subject: [PATCH 08/16] feat: dontSubtractVendor{Credit,Platinum,Item,Standing}Cost cheats (#2209) Closes #1586 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2209 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 4 ++ src/services/configService.ts | 4 ++ src/services/purchaseService.ts | 90 +++++++++++++++++++-------------- static/webui/index.html | 20 +++++++- static/webui/translations/de.js | 6 ++- static/webui/translations/en.js | 6 ++- static/webui/translations/es.js | 4 ++ static/webui/translations/fr.js | 6 ++- static/webui/translations/ru.js | 6 ++- static/webui/translations/zh.js | 6 ++- 10 files changed, 106 insertions(+), 46 deletions(-) diff --git a/config.json.example b/config.json.example index 4c5fafb5..8dfdfd31 100644 --- a/config.json.example +++ b/config.json.example @@ -20,6 +20,10 @@ "infiniteRegalAya": false, "infiniteHelminthMaterials": false, "claimingBlueprintRefundsIngredients": false, + "dontSubtractPurchaseCreditCost": false, + "dontSubtractPurchasePlatinumCost": false, + "dontSubtractPurchaseItemCost": false, + "dontSubtractPurchaseStandingCost": false, "dontSubtractVoidTraces": false, "dontSubtractConsumables": false, "unlockAllShipFeatures": false, diff --git a/src/services/configService.ts b/src/services/configService.ts index a45645a2..b14ffb67 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -25,6 +25,10 @@ export interface IConfig { infiniteRegalAya?: boolean; infiniteHelminthMaterials?: boolean; claimingBlueprintRefundsIngredients?: boolean; + dontSubtractPurchaseCreditCost?: boolean; + dontSubtractPurchasePlatinumCost?: boolean; + dontSubtractPurchaseItemCost?: boolean; + dontSubtractPurchaseStandingCost?: boolean; dontSubtractVoidTraces?: boolean; dontSubtractConsumables?: boolean; unlockAllShipFeatures?: boolean; diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 59a431c3..4fb5bb2d 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -67,25 +67,31 @@ export const handlePurchase = async ( if (!offer) { throw new Error(`unknown vendor offer: ${ItemId ? ItemId : purchaseRequest.PurchaseParams.StoreItem}`); } - if (offer.RegularPrice) { - combineInventoryChanges( - prePurchaseInventoryChanges, - updateCurrency(inventory, offer.RegularPrice[0], false) - ); + if (!config.dontSubtractPurchaseCreditCost) { + if (offer.RegularPrice) { + combineInventoryChanges( + prePurchaseInventoryChanges, + updateCurrency(inventory, offer.RegularPrice[0], false) + ); + } } - if (offer.PremiumPrice) { - combineInventoryChanges( - prePurchaseInventoryChanges, - updateCurrency(inventory, offer.PremiumPrice[0], true) - ); + if (!config.dontSubtractPurchasePlatinumCost) { + if (offer.PremiumPrice) { + combineInventoryChanges( + prePurchaseInventoryChanges, + updateCurrency(inventory, offer.PremiumPrice[0], true) + ); + } } - if (offer.ItemPrices) { - handleItemPrices( - inventory, - offer.ItemPrices, - purchaseRequest.PurchaseParams.Quantity, - prePurchaseInventoryChanges - ); + if (!config.dontSubtractPurchaseItemCost) { + if (offer.ItemPrices) { + handleItemPrices( + inventory, + offer.ItemPrices, + purchaseRequest.PurchaseParams.Quantity, + prePurchaseInventoryChanges + ); + } } if (offer.LocTagRandSeed !== undefined) { seed = BigInt(offer.LocTagRandSeed); @@ -179,21 +185,25 @@ export const handlePurchase = async ( x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem ); if (offer) { - combineInventoryChanges( - purchaseResponse.InventoryChanges, - updateCurrency(inventory, offer.RegularPrice, false) - ); + if (!config.dontSubtractPurchaseCreditCost) { + combineInventoryChanges( + purchaseResponse.InventoryChanges, + updateCurrency(inventory, offer.RegularPrice, false) + ); + } if (purchaseRequest.PurchaseParams.ExpectedPrice) { throw new Error(`vendor purchase should not have an expected price`); } - const invItem: IMiscItem = { - ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks", - ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1 - }; - addMiscItems(inventory, [invItem]); - purchaseResponse.InventoryChanges.MiscItems ??= []; - purchaseResponse.InventoryChanges.MiscItems.push(invItem); + if (!config.dontSubtractPurchaseItemCost) { + const invItem: IMiscItem = { + ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks", + ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1 + }; + addMiscItems(inventory, [invItem]); + purchaseResponse.InventoryChanges.MiscItems ??= []; + purchaseResponse.InventoryChanges.MiscItems.push(invItem); + } } break; } @@ -211,7 +221,7 @@ export const handlePurchase = async ( Title: lastTitle } ]; - } else { + } else if (!config.dontSubtractPurchaseStandingCost) { const syndicate = ExportSyndicates[syndicateTag]; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (syndicate) { @@ -239,19 +249,19 @@ export const handlePurchase = async ( const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!]; const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem); if (offer) { - if (typeof offer.credits == "number") { + if (typeof offer.credits == "number" && !config.dontSubtractPurchaseCreditCost) { combineInventoryChanges( purchaseResponse.InventoryChanges, updateCurrency(inventory, offer.credits, false) ); } - if (typeof offer.platinum == "number") { + if (typeof offer.platinum == "number" && !config.dontSubtractPurchasePlatinumCost) { combineInventoryChanges( purchaseResponse.InventoryChanges, updateCurrency(inventory, offer.platinum, true) ); } - if (offer.itemPrices) { + if (offer.itemPrices && !config.dontSubtractPurchaseItemCost) { handleItemPrices( inventory, offer.itemPrices, @@ -278,15 +288,17 @@ export const handlePurchase = async ( ); if (offer) { if (offer.RegularPrice) { - const invItem: IMiscItem = { - ItemType: "/Lotus/Types/Items/MiscItems/SchismKey", - ItemCount: offer.RegularPrice * purchaseRequest.PurchaseParams.Quantity * -1 - }; + if (!config.dontSubtractPurchaseItemCost) { + const invItem: IMiscItem = { + 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.push(invItem); + purchaseResponse.InventoryChanges.MiscItems ??= []; + purchaseResponse.InventoryChanges.MiscItems.push(invItem); + } } else if (!config.infiniteRegalAya) { inventory.PrimeTokens -= offer.PrimePrice! * purchaseRequest.PurchaseParams.Quantity; diff --git a/static/webui/index.html b/static/webui/index.html index 9b9015a2..b6d87db4 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -604,13 +604,29 @@ +
+ + +
- - + + +
+
+ + +
+
+ + +
+
+ +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index c42d3cf9..6672d726 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -135,11 +135,15 @@ dict = { cheats_infiniteRegalAya: `Unendlich Reines Aya`, cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`, cheats_claimingBlueprintRefundsIngredients: `Fertige Blaupausen erstatten Ressourcen zurück`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`, cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`, cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`, cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`, - cheats_unlockAllFlavourItems: `Alle Sammlerstücke freischalten`, + cheats_unlockAllFlavourItems: `Alle Sammlerstücke freischalten`, cheats_unlockAllSkins: `Alle Skins freischalten`, cheats_unlockAllCapturaScenes: `Alle Photora-Szenen freischalten`, cheats_unlockAllDecoRecipes: `Alle Dojo-Deko-Baupläne freischalten`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index aa35d2a9..f7869ac2 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -134,11 +134,15 @@ dict = { cheats_infiniteRegalAya: `Infinite Regal Aya`, cheats_infiniteHelminthMaterials: `Infinite Helminth Materials`, cheats_claimingBlueprintRefundsIngredients: `Claiming Blueprint Refunds Ingredients`, + cheats_dontSubtractPurchaseCreditCost: `Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `Don't Subtract Void Traces`, cheats_dontSubtractConsumables: `Don't Subtract Consumables`, cheats_unlockAllShipFeatures: `Unlock All Ship Features`, cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`, - cheats_unlockAllFlavourItems: `Unlock All Flavor Items`, + cheats_unlockAllFlavourItems: `Unlock All Flavor Items`, cheats_unlockAllSkins: `Unlock All Skins`, cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`, cheats_unlockAllDecoRecipes: `Unlock All Dojo Deco Recipes`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index d3eb257b..79aedacd 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -135,6 +135,10 @@ dict = { cheats_infiniteRegalAya: `Aya Real infinita`, cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`, cheats_claimingBlueprintRefundsIngredients: `Reclamar ingredientes devueltos por planos`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `No descontar vestigios del Vacío`, cheats_dontSubtractConsumables: `No restar consumibles`, cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index e4fb0f3f..c605fce8 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -135,11 +135,15 @@ dict = { cheats_infiniteRegalAya: `Aya Raffiné infini`, cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`, cheats_claimingBlueprintRefundsIngredients: `Récupérer les items rend les ressources`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`, cheats_dontSubtractConsumables: `Ne pas retirer de consommables`, cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`, cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`, - cheats_unlockAllFlavourItems: `Débloquer tous les Flavor Items`, + cheats_unlockAllFlavourItems: `Débloquer tous les Flavor Items`, cheats_unlockAllSkins: `Débloquer tous les skins`, cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`, cheats_unlockAllDecoRecipes: `Débloquer toutes les recherches dojo`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index e50f2959..cb89fa7d 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -135,11 +135,15 @@ dict = { cheats_infiniteRegalAya: `Бесконечная Королевская Айя`, cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`, cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`, cheats_dontSubtractConsumables: `Не уменьшать количество расходников`, cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`, cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`, - cheats_unlockAllFlavourItems: `Разблокировать все уникальные предметы`, + cheats_unlockAllFlavourItems: `Разблокировать все уникальные предметы`, cheats_unlockAllSkins: `Разблокировать все скины`, cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`, cheats_unlockAllDecoRecipes: `Разблокировать все рецепты декораций Дoдзё`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 4b763583..06b2ece9 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -135,11 +135,15 @@ dict = { cheats_infiniteRegalAya: `无限御品阿耶`, cheats_infiniteHelminthMaterials: `无限Helminth材料`, cheats_claimingBlueprintRefundsIngredients: `取消蓝图制造时返还材料`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `虚空光体无消耗`, cheats_dontSubtractConsumables: `消耗物品使用时无损耗`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipDecorations: `解锁所有飞船装饰`, - cheats_unlockAllFlavourItems: `解锁所有装饰物品`, + cheats_unlockAllFlavourItems: `解锁所有装饰物品`, cheats_unlockAllSkins: `解锁所有外观`, cheats_unlockAllCapturaScenes: `解锁所有Captura场景`, cheats_unlockAllDecoRecipes: `解锁所有道场配方`, From e686a2d028cfada2e11ff80afb73c9055bcbc7ba Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:18:49 -0700 Subject: [PATCH 09/16] chore: report unknown fields in updateChallengeProgress payload (#2210) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2210 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/updateChallengeProgressController.ts | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/controllers/api/updateChallengeProgressController.ts b/src/controllers/api/updateChallengeProgressController.ts index 51eb88c3..5a7e6467 100644 --- a/src/controllers/api/updateChallengeProgressController.ts +++ b/src/controllers/api/updateChallengeProgressController.ts @@ -4,6 +4,8 @@ import { getAccountForRequest } from "@/src/services/loginService"; import { addChallenges, getInventory } from "@/src/services/inventoryService"; import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes"; 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) => { const challenges = getJSONfromString(String(req.body)); @@ -13,9 +15,6 @@ export const updateChallengeProgressController: RequestHandler = async (req, res account._id.toString(), "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations" ); - if (challenges.ChallengesFixVersion !== undefined) { - inventory.ChallengesFixVersion = challenges.ChallengesFixVersion; - } let affiliationMods: IAffiliationMods[] = []; if (challenges.ChallengeProgress) { affiliationMods = addChallenges( @@ -25,15 +24,30 @@ export const updateChallengeProgressController: RequestHandler = async (req, res challenges.SeasonChallengeCompletions ); } - if (challenges.SeasonChallengeHistory) { - challenges.SeasonChallengeHistory.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 }); - } - }); + for (const [key, value] of getEntriesUnsafe(challenges)) { + switch (key) { + 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 "ChallengeProgress": + case "SeasonChallengeCompletions": + case "ChallengePTS": + break; + default: + logger.warn(`unknown challenge progress entry`, { key, value }); + } } await inventory.save(); @@ -43,6 +57,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res }; interface IUpdateChallengeProgressRequest { + ChallengePTS?: number; ChallengesFixVersion?: number; ChallengeProgress?: IChallengeProgress[]; SeasonChallengeHistory?: ISeasonChallenge[]; From 3186ffe164f100cd323320019bc23d376fe2c764 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:39:50 -0700 Subject: [PATCH 10/16] feat: autogenerate temple & archimedean vendors (#2208) So the kuva offer is refreshed every week. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2208 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/serversideVendorsService.ts | 61 ++- .../ArchimedeanVendorManifest.json | 38 -- .../Temple1999VendorManifest.json | 459 ------------------ .../ZarimanCommisionsManifestArchimedean.json | 75 --- 4 files changed, 30 insertions(+), 603 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 1c947197..6205060f 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -7,7 +7,6 @@ import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorT import { logger } from "@/src/utils/logger"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; -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"; @@ -23,11 +22,8 @@ import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSal import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; -import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json"; -import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json"; const rawVendorManifests: IVendorManifest[] = [ - ArchimedeanVendorManifest, DeimosEntratiFragmentVendorProductsManifest, DeimosHivemindCommisionsManifestFishmonger, DeimosHivemindCommisionsManifestPetVendor, @@ -42,9 +38,7 @@ const rawVendorManifests: IVendorManifest[] = [ MaskSalesmanManifest, Nova1999ConquestShopManifest, OstronPetVendorManifest, - SolarisDebtTokenVendorRepossessionsManifest, - Temple1999VendorManifest, - ZarimanCommisionsManifestArchimedean + SolarisDebtTokenVendorRepossessionsManifest ]; interface IGeneratableVendorInfo extends Omit { @@ -297,31 +291,30 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } // Add counted offers - if (manifest.numItems) { - const useRng = manifest.numItems.minValue != manifest.numItems.maxValue; - const numItemsTarget = - numUncountedOffers + - (useRng - ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue) - : manifest.numItems.minValue); - let i = 0; - while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { - const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++]; - if ( - !item.alwaysOffered && - 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 == manifest.items.length) { - i = 0; + const useRng = manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue; + const numItemsTarget = manifest.numItems + ? numUncountedOffers + + (useRng + ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue) + : manifest.numItems.minValue) + : manifest.items.length; + let i = 0; + while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { + const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++]; + if ( + !item.alwaysOffered && + 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 == manifest.items.length) { + i = 0; } } } else { @@ -465,4 +458,10 @@ if (isDev) { ) { logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`); } + + const temple = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest")! + .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`); + } } diff --git a/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json b/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json deleted file mode 100644 index df3dc048..00000000 --- a/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "VendorInfo": { - "_id": { "$oid": "62695b0467e5d379750f9f75" }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Zariman/ArchimedeanVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/VoidPlumeAOrnament", - "ItemPrices": [{ "ItemCount": 1, "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidAngelItem", "ProductCategory": "MiscItems" }], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "AllowMultipurchase": true, - "Id": { "$oid": "63ed01ef4c37f93d0b797826" } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/VoidPlumeBOrnament", - "ItemPrices": [{ "ItemCount": 1, "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidAngelItem", "ProductCategory": "MiscItems" }], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "AllowMultipurchase": true, - "Id": { "$oid": "63ed01ef4c37f93d0b797827" } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [{ "ItemCount": 5, "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidAngelItem", "ProductCategory": "MiscItems" }], - "Bin": "BIN_0", - "QuantityMultiplier": 35000, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "66664112af1177b5070ab882" } - } - ], - "PropertyTextHash": "DB7BF03C3FE6D0036A4DC30066A9A17E", - "Expiry": { "$date": { "$numberLong": "9999999000000" } } - } -} diff --git a/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json b/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json deleted file mode 100644 index 6309363f..00000000 --- a/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json +++ /dev/null @@ -1,459 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "67dadc30e4b6e0e5979c8d56" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleBlueprint", - "ItemPrices": [ - { - "ItemCount": 195, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c18c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleSystemsBlueprint", - "ItemPrices": [ - { - "ItemCount": 65, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c18d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleChassisBlueprint", - "ItemPrices": [ - { - "ItemCount": 65, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c18e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleHelmetBlueprint", - "ItemPrices": [ - { - "ItemCount": 65, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c18f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/1999EntHybridPistolBlueprint", - "ItemPrices": [ - { - "ItemCount": 120, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c190" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolBarrelBlueprint", - "ItemPrices": [ - { - "ItemCount": 60, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c191" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolReceiverBlueprint", - "ItemPrices": [ - { - "ItemCount": 60, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c192" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolStockBlueprint", - "ItemPrices": [ - { - "ItemCount": 60, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c193" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumCoreKitA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c194" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumCymbalA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c195" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumFloorTomA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c196" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumSnareA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c197" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c198" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseB", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c199" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseC", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19a" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseD", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19b" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseE", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseF", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumSynthKeyboardA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/PhotoBooth/Vania/PhotoboothTileVaniaObjTempleDefense", - "ItemPrices": [ - { - "ItemCount": 100, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": false, - "Id": { - "$oid": "67dadc30641da66dc5c1c19f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [ - { - "ItemCount": 110, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 6000, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 7, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c1a5" - } - } - ], - "PropertyTextHash": "20B13D9EB78FEC80EA32D0687F5BA1AE", - "RequiredGoalTag": "", - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json b/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json deleted file mode 100644 index ec1c5c22..00000000 --- a/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "VendorInfo": { - "_id": { "$oid": "62a20ba667e5d3797540d831" }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Zariman/ZarimanCommisionsManifestArchimedean", - "ItemManifest": [ - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskE", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB", "ItemCount": 4, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemA", "ItemCount": 6, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_4", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c2597299" } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskD", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemA", "ItemCount": 5, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB", "ItemCount": 3, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_3", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c259729a" } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskC", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidWraithItem", "ItemCount": 15, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanDogTagUncommon", "ItemCount": 1, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c259729b" } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskB", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidWraithItem", "ItemCount": 4, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB", "ItemCount": 1, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c259729c" } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskA", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB", "ItemCount": 1, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemA", "ItemCount": 2, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c259729d" } - } - ], - "PropertyTextHash": "F43F0ED811985EEF856970A8342EF322", - "Expiry": { "$date": { "$numberLong": "9999999000000" } } - } -} From ce46fa14ac77dfb9bc2abbe35a165b9264620602 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 05:04:24 +0200 Subject: [PATCH 11/16] chore: ignore crossPlaySetting in updateChallengeProgress --- src/controllers/api/updateChallengeProgressController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/api/updateChallengeProgressController.ts b/src/controllers/api/updateChallengeProgressController.ts index 5a7e6467..9b7292e6 100644 --- a/src/controllers/api/updateChallengeProgressController.ts +++ b/src/controllers/api/updateChallengeProgressController.ts @@ -44,6 +44,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res case "ChallengeProgress": case "SeasonChallengeCompletions": case "ChallengePTS": + case "crossPlaySetting": break; default: logger.warn(`unknown challenge progress entry`, { key, value }); @@ -62,4 +63,5 @@ interface IUpdateChallengeProgressRequest { ChallengeProgress?: IChallengeProgress[]; SeasonChallengeHistory?: ISeasonChallenge[]; SeasonChallengeCompletions?: ISeasonChallenge[]; + crossPlaySetting?: string; } From c6c7a2966b22218ae84dcf4ee8a47a9810292126 Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:40:27 -0700 Subject: [PATCH 12/16] fix: deimos vault bounty detection (#2207) Related to #388 this should fix incorect rewards for deimos filed bounties Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2207 Reviewed-by: Sainan Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> --- src/services/missionInventoryUpdateService.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 3d1763a2..b8dc4c67 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -1204,8 +1204,10 @@ export const addMissionRewards = async ( if (syndicateEntry && syndicateEntry.Jobs) { let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!]; if (syndicateEntry.Tag === "EntratiSyndicate") { - const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); - if (vault) currentJob = vault; + if (jobType.endsWith("VaultBounty")) { + const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); + if (vault) currentJob = vault; + } let medallionAmount = Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)); if ( @@ -1552,8 +1554,10 @@ function getRandomMissionDrops( let job = syndicateEntry.Jobs[RewardInfo.JobTier!]; if (syndicateEntry.Tag === "EntratiSyndicate") { - const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); - if (vault && locationTag) job = vault; + if (jobType.endsWith("VaultBounty")) { + const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); + if (vault) job = vault; + } // if ( // [ // "DeimosRuinsExterminateBounty", From 97064826b22d2ef4c123eff71cf1dcfba59d5db9 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:41:13 -0700 Subject: [PATCH 13/16] feat: unlockAllProfitTakerStages cheat (#2215) Closes #2081 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2215 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 1 + src/controllers/api/inventoryController.ts | 20 ++++++++++++++++++++ src/services/configService.ts | 1 + static/webui/index.html | 4 ++++ static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 10 files changed, 32 insertions(+) diff --git a/config.json.example b/config.json.example index 8dfdfd31..e35b62df 100644 --- a/config.json.example +++ b/config.json.example @@ -43,6 +43,7 @@ "noDeathMarks": false, "noKimCooldowns": false, "syndicateMissionsRepeatable": false, + "unlockAllProfitTakerStages": false, "instantFinishRivenChallenge": false, "instantResourceExtractorDrones": false, "noResourceExtractorDronesDamage": false, diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index df04e9d4..cdaf8d4d 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -339,9 +339,29 @@ 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 + }); + } + } + return inventoryResponse; }; +const allEudicoHeistJobs = [ + "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne", + "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo", + "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree", + "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour" +]; + const addString = (arr: string[], str: string): void => { if (arr.indexOf(str) == -1) { arr.push(str); diff --git a/src/services/configService.ts b/src/services/configService.ts index b14ffb67..20515ee2 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -49,6 +49,7 @@ export interface IConfig { noDeathMarks?: boolean; noKimCooldowns?: boolean; syndicateMissionsRepeatable?: boolean; + unlockAllProfitTakerStages?: boolean; instantFinishRivenChallenge?: boolean; instantResourceExtractorDrones?: boolean; noResourceExtractorDronesDamage?: boolean; diff --git a/static/webui/index.html b/static/webui/index.html index b6d87db4..3e0856bf 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -708,6 +708,10 @@
+
+ + +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 6672d726..bb7ec5dc 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `Keine Todesmarkierungen`, cheats_noKimCooldowns: `Keine Wartezeit bei KIM`, cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index f7869ac2..7a227acb 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -158,6 +158,7 @@ dict = { cheats_noDeathMarks: `No Death Marks`, cheats_noKimCooldowns: `No KIM Cooldowns`, cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`, + cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`, cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`, cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 79aedacd..5ee94df3 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `Sin marcas de muerte`, cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`, cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`, cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`, cheats_noResourceExtractorDronesDamage: `Sin daño a los drones extractores de recursos`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index c605fce8..a4814e2f 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `Aucune marque d'assassin`, cheats_noKimCooldowns: `Aucun cooldown sur le KIM`, cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`, cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`, cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index cb89fa7d..22277b35 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `Без меток сметри`, cheats_noKimCooldowns: `Чаты KIM без кулдауна`, cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`, cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`, cheats_noResourceExtractorDronesDamage: `Без урона по дронам-сборщикам`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 06b2ece9..44917ae8 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`, cheats_noKimCooldowns: `无 KIM 冷却时间`, cheats_syndicateMissionsRepeatable: `集团任务可重复`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`, cheats_instantResourceExtractorDrones: `即时资源采集无人机`, cheats_noResourceExtractorDronesDamage: `资源提取器不会损毁`, From 3619bdfdb50208e85ea5c3b61b07b82ae4794637 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:41:47 -0700 Subject: [PATCH 14/16] feat: autogenerate mask vendor (#2216) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2216 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/serversideVendorsService.ts | 49 ++- .../getVendorInfo/MaskSalesmanManifest.json | 301 ------------------ 2 files changed, 37 insertions(+), 313 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 6205060f..774180d6 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -18,7 +18,6 @@ import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/Deim 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 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 SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; @@ -35,7 +34,6 @@ const rawVendorManifests: IVendorManifest[] = [ DuviriAcrithisVendorManifest, EntratiLabsEntratiLabsCommisionsManifest, EntratiLabsEntratiLabVendorManifest, - MaskSalesmanManifest, Nova1999ConquestShopManifest, OstronPetVendorManifest, SolarisDebtTokenVendorRepossessionsManifest @@ -277,6 +275,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani // Add permanent offers let numUncountedOffers = 0; + let numCountedOffers = 0; let offset = 0; for (const item of manifest.items) { if (item.alwaysOffered || item.rotatedWeekly) { @@ -287,11 +286,16 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani offersToAdd.push(item); ++offset; } + } else { + numCountedOffers += 1 + item.duplicates; } } // Add counted offers - const useRng = manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue; + const useRng = + manifest.numItems && + (manifest.numItems.minValue != manifest.numItems.maxValue || + manifest.numItems.minValue != numCountedOffers); const numItemsTarget = manifest.numItems ? numUncountedOffers + (useRng @@ -299,10 +303,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani : 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.randomElement(manifest.items)! : manifest.items[i++]; + const item = useRng ? rng.randomReward(rollableOffers)! : rollableOffers[i++]; if ( - !item.alwaysOffered && remainingItemCapacity[getOfferId(item)] != 0 && (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin]) ) { @@ -313,7 +320,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } offersToAdd.splice(offset, 0, item); } - if (i == manifest.items.length) { + if (i == rollableOffers.length) { i = 0; } } @@ -351,7 +358,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } }; if (rawItem.numRandomItemPrices) { - item.ItemPrices = []; + item.ItemPrices ??= []; for (let i = 0; i != rawItem.numRandomItemPrices; ++i) { let itemPrice: { type: string; count: IRange }; do { @@ -391,11 +398,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani info.ItemManifest.push(item); } - 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; - }); + 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 let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER; @@ -464,4 +473,20 @@ if (isDev) { 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")! + .VendorInfo.ItemManifest; + if ( + nakak.length != 10 || + nakak[0].StoreItem != "/Lotus/StoreItems/Upgrades/Skins/Ostron/RevenantMask" || + nakak[1].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumper" || + nakak[1].ItemPrices?.length != 4 || + nakak[2].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperMedium" || + 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/Ostron/MaskSalesmanManifest`); + } } diff --git a/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json b/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json deleted file mode 100644 index 85aa7eac..00000000 --- a/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json +++ /dev/null @@ -1,301 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "598a090d9a4a313746fd1f24" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/RevenantMask", - "ItemPrices": [ - { - "ItemCount": 1, - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "63ed01ef4c37f93d0b797674" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumper", - "ItemPrices": [ - { - "ItemCount": 2, - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/NistlebrushItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem", - "ItemCount": 32, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "63ed01ef4c37f93d0b797675" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperMedium", - "ItemPrices": [ - { - "ItemCount": 4, - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonGemBCutAItem", - "ItemCount": 24, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishAPartItem", - "ItemCount": 18, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", - "ItemCount": 27, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "63ed01ef4c37f93d0b797676" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperLarge", - "ItemPrices": [ - { - "ItemCount": 6, - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonGemACutAItem", - "ItemCount": 35, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothCommonFishAPartItem", - "ItemCount": 95, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/NistlebrushItem", - "ItemCount": 60, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "63ed01ef4c37f93d0b797677" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/SynthicatorRecipes/FlareBlueBlueprint", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishCPartItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a1" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/SynthicatorRecipes/FlareRedBlueprint", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem", - "ItemCount": 37, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishAPartItem", - "ItemCount": 7, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a2" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/VoltMask", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreBAlloyBItem", - "ItemCount": 34, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem", - "ItemCount": 17, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a3" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/MagMask", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem", - "ItemCount": 16, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/ForestRodentPartItem", - "ItemCount": 5, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a4" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/ExcaliburMask", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/BirdOfPreyPartItem", - "ItemCount": 5, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a5" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/GrineerMask", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem", - "ItemCount": 31, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a6" - } - } - ], - "PropertyTextHash": "6AACA376DA34B35B5C16F1B40DBC017D", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} From 3c64f17e34fcdaaaeadb8c6f4cd95e80de9ec40a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:42:11 -0700 Subject: [PATCH 15/16] feat: missionsCanGiveAllRelics cheat (#2217) Closes #1060 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2217 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 1 + src/services/configService.ts | 1 + src/services/missionInventoryUpdateService.ts | 18 ++++++++++++++++++ static/webui/index.html | 4 ++++ static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 10 files changed, 30 insertions(+) diff --git a/config.json.example b/config.json.example index e35b62df..a92e3f8d 100644 --- a/config.json.example +++ b/config.json.example @@ -54,6 +54,7 @@ "noDojoResearchCosts": false, "noDojoResearchTime": false, "fastClanAscension": false, + "missionsCanGiveAllRelics": false, "spoofMasteryRank": -1, "nightwaveStandingMultiplier": 1, "worldState": { diff --git a/src/services/configService.ts b/src/services/configService.ts index 20515ee2..b4566096 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -60,6 +60,7 @@ export interface IConfig { noDojoResearchCosts?: boolean; noDojoResearchTime?: boolean; fastClanAscension?: boolean; + missionsCanGiveAllRelics?: boolean; spoofMasteryRank?: number; nightwaveStandingMultiplier?: number; worldState?: { diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index b8dc4c67..499115f9 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -2,6 +2,7 @@ import { ExportEnemies, ExportFusionBundles, ExportRegions, + ExportRelics, ExportRewards, IMissionReward as IMissionRewardExternal, IRegion, @@ -1805,6 +1806,23 @@ function getRandomMissionDrops( 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; } diff --git a/static/webui/index.html b/static/webui/index.html index 3e0856bf..e80d9fe7 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -752,6 +752,10 @@
+
+ + +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index bb7ec5dc..b84df0f2 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `Keine Dojo-Forschungskosten`, cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`, cheats_fastClanAscension: `Schneller Clan-Aufstieg`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_save: `[UNTRANSLATED] Save`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 7a227acb..7f5b5aca 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -169,6 +169,7 @@ dict = { cheats_noDojoResearchCosts: `No Dojo Research Costs`, cheats_noDojoResearchTime: `No Dojo Research Time`, cheats_fastClanAscension: `Fast Clan Ascension`, + cheats_missionsCanGiveAllRelics: `Missions Can Give All Relics`, cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`, cheats_save: `Save`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 5ee94df3..da50fe7e 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `Sin costo de investigación del dojo`, cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`, cheats_fastClanAscension: `Ascenso rápido del clan`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`, cheats_save: `Guardar`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index a4814e2f..4a8fae03 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `Aucun coût de recherche (Dojo)`, cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_fastClanAscension: `Ascension de clan rapide`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_save: `[UNTRANSLATED] Save`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 22277b35..612555b3 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `Бесплатные Исследование Додзё`, cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`, cheats_fastClanAscension: `Мгновенное Вознесение Клана`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_save: `[UNTRANSLATED] Save`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 44917ae8..6b1d4b7f 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `无视道场研究消耗`, cheats_noDojoResearchTime: `无视道场研究时间`, cheats_fastClanAscension: `快速升级氏族`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`, cheats_save: `[UNTRANSLATED] Save`, From 95136e6059b0d456d6e1174d6b2527c6944b70a6 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:47:45 -0700 Subject: [PATCH 16/16] feat: dynamic void fissure missions (#2214) Closes #1512 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2214 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../dynamic/worldStateController.ts | 15 +- src/index.ts | 6 + src/models/worldStateModel.ts | 14 ++ src/services/worldStateService.ts | 91 ++++++++- src/types/worldStateTypes.ts | 10 +- .../worldState/fissureMissions.json | 154 ++++++++++++++ .../worldState/worldState.json | 189 ------------------ 7 files changed, 276 insertions(+), 203 deletions(-) create mode 100644 src/models/worldStateModel.ts create mode 100644 static/fixed_responses/worldState/fissureMissions.json diff --git a/src/controllers/dynamic/worldStateController.ts b/src/controllers/dynamic/worldStateController.ts index 89335497..75d78ff0 100644 --- a/src/controllers/dynamic/worldStateController.ts +++ b/src/controllers/dynamic/worldStateController.ts @@ -1,6 +1,15 @@ import { RequestHandler } from "express"; -import { getWorldState } from "@/src/services/worldStateService"; +import { getWorldState, populateFissures } from "@/src/services/worldStateService"; +import { version_compare } from "@/src/helpers/inventoryHelpers"; -export const worldStateController: RequestHandler = (req, res) => { - res.json(getWorldState(req.query.buildLabel as string | undefined)); +export const worldStateController: RequestHandler = async (req, res) => { + const buildLabel = req.query.buildLabel as string | undefined; + const worldState = getWorldState(buildLabel); + + // Omitting void fissures for versions prior to Dante Unbound to avoid script errors. + if (!buildLabel || version_compare(buildLabel, "2024.03.24.20.00") >= 0) { + await populateFissures(worldState); + } + + res.json(worldState); }; diff --git a/src/index.ts b/src/index.ts index de36b392..7afd9387 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,7 @@ import { JSONStringify } from "json-with-bigint"; import { startWebServer } from "./services/webService"; import { validateConfig } from "@/src/services/configWatcherService"; +import { updateWorldStateCollections } from "./services/worldStateService"; // Patch JSON.stringify to work flawlessly with Bigints. JSON.stringify = JSONStringify; @@ -33,6 +34,11 @@ mongoose .then(() => { logger.info("Connected to MongoDB"); startWebServer(); + + void updateWorldStateCollections(); + setInterval(() => { + void updateWorldStateCollections(); + }, 60_000); }) .catch(error => { if (error instanceof Error) { diff --git a/src/models/worldStateModel.ts b/src/models/worldStateModel.ts new file mode 100644 index 00000000..37615d7e --- /dev/null +++ b/src/models/worldStateModel.ts @@ -0,0 +1,14 @@ +import { IFissureDatabase } from "@/src/types/worldStateTypes"; +import { model, Schema } from "mongoose"; + +const fissureSchema = new Schema({ + 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("Fissure", fissureSchema); diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 8fa82eed..ea48ef44 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1,12 +1,13 @@ import staticWorldState from "@/static/fixed_responses/worldState/worldState.json"; +import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json"; import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json"; import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json"; import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json"; import { buildConfig } from "@/src/services/buildConfigService"; import { unixTimesInMs } from "@/src/constants/timeConstants"; import { config } from "@/src/services/configService"; -import { SRng } from "@/src/services/rngService"; -import { ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus"; +import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService"; +import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus"; import { ICalendarDay, ICalendarEvent, @@ -21,8 +22,9 @@ import { IWorldState, TCircuitGameMode } from "../types/worldStateTypes"; -import { version_compare } from "../helpers/inventoryHelpers"; +import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers"; import { logger } from "../utils/logger"; +import { Fissure } from "../models/worldStateModel"; const sortieBosses = [ "SORTIE_BOSS_HYENA", @@ -1110,6 +1112,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Alerts: [], Sorties: [], LiteSorties: [], + ActiveMissions: [], GlobalUpgrades: [], VoidStorms: [], EndlessXpChoices: [], @@ -1118,13 +1121,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => { SyndicateMissions: [...staticWorldState.SyndicateMissions] }; - // Omit void fissures for versions prior to Dante Unbound to avoid script errors. - if (buildLabel && version_compare(buildLabel, "2024.03.24.20.00") < 0) { - worldState.ActiveMissions = []; - 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 = []; - } + // Old versions seem to really get hung up on not being able to load these. + if (buildLabel && version_compare(buildLabel, "2017.10.12.17.04") < 0) { + worldState.PVPChallengeInstances = []; } if (config.worldState?.starDays) { @@ -1364,6 +1363,24 @@ export const getWorldState = (buildLabel?: string): IWorldState => { return worldState; }; +export const populateFissures = async (worldState: IWorldState): Promise => { + 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 idToBountyCycle = (id: string): number => { return Math.trunc((parseInt(id.substring(0, 8), 16) * 1000) / 9000_000); }; @@ -1491,3 +1508,57 @@ const nightwaveTagToSeason: Record = { RadioLegionIntermissionSyndicate: 1, // Intermission I RadioLegionSyndicate: 0 // The Wolf of Saturn Six }; + +export const updateWorldStateCollections = async (): Promise => { + const fissures = await Fissure.find(); + + const activeNodes = new Set(); + const tierToFurthestExpiry: Record = { + 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 + }); + } + } + } +}; diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index bd8ab138..73aa9d78 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -9,8 +9,8 @@ export interface IWorldState { Sorties: ISortie[]; LiteSorties: ILiteSortie[]; SyndicateMissions: ISyndicateMissionInfo[]; - GlobalUpgrades: IGlobalUpgrade[]; ActiveMissions: IFissure[]; + GlobalUpgrades: IGlobalUpgrade[]; NodeOverrides: INodeOverride[]; VoidStorms: IVoidStorm[]; PVPChallengeInstances: IPVPChallengeInstance[]; @@ -86,6 +86,14 @@ export interface IFissure { Hard?: boolean; } +export interface IFissureDatabase { + Activation: Date; + Expiry: Date; + Node: string; + Modifier: "VoidT1" | "VoidT2" | "VoidT3" | "VoidT4" | "VoidT5" | "VoidT6"; + Hard?: boolean; +} + export interface INodeOverride { _id: IOid; Activation?: IMongoDate; diff --git a/static/fixed_responses/worldState/fissureMissions.json b/static/fixed_responses/worldState/fissureMissions.json new file mode 100644 index 00000000..9b5c85e3 --- /dev/null +++ b/static/fixed_responses/worldState/fissureMissions.json @@ -0,0 +1,154 @@ +{ + "VoidT1": [ + "SolNode23", + "SolNode66", + "SolNode45", + "SolNode41", + "SolNode59", + "SolNode39", + "SolNode75", + "SolNode113", + "SolNode85", + "SolNode58", + "SolNode101", + "SolNode109", + "SolNode26", + "SolNode15", + "SolNode61", + "SolNode123", + "SolNode16", + "SolNode79", + "SolNode2", + "SolNode22", + "SolNode68", + "SolNode89", + "SolNode11", + "SolNode46", + "SolNode36", + "SolNode27", + "SolNode14", + "SolNode106", + "SolNode30", + "SolNode107", + "SolNode63", + "SolNode128" + ], + "VoidT2": [ + "SolNode141", + "SolNode149", + "SolNode10", + "SolNode93", + "SettlementNode11", + "SolNode137", + "SolNode132", + "SolNode73", + "SolNode82", + "SolNode25", + "SolNode88", + "SolNode126", + "SolNode135", + "SolNode74", + "SettlementNode15", + "SolNode147", + "SolNode67", + "SolNode20", + "SolNode42", + "SolNode18", + "SolNode31", + "SolNode139", + "SettlementNode12", + "SolNode100", + "SolNode140", + "SolNode70", + "SettlementNode1", + "SettlementNode14", + "SolNode50", + "SettlementNode2", + "SolNode146", + "SettlementNode3", + "SolNode97", + "SolNode125", + "SolNode19", + "SolNode121", + "SolNode96", + "SolNode131" + ], + "VoidT3": [ + "SolNode62", + "SolNode17", + "SolNode403", + "SolNode6", + "SolNode118", + "SolNode211", + "SolNode217", + "SolNode401", + "SolNode64", + "SolNode405", + "SolNode84", + "SolNode402", + "SolNode408", + "SolNode122", + "SolNode57", + "SolNode216", + "SolNode205", + "SolNode215", + "SolNode404", + "SolNode209", + "SolNode406", + "SolNode204", + "SolNode203", + "SolNode409", + "SolNode400", + "SolNode212", + "SolNode1", + "SolNode412", + "SolNode49", + "SolNode78", + "SolNode410", + "SolNode407", + "SolNode220" + ], + "VoidT4": [ + "SolNode188", + "SolNode403", + "SolNode189", + "SolNode21", + "SolNode102", + "SolNode171", + "SolNode196", + "SolNode184", + "SolNode185", + "SolNode76", + "SolNode195", + "SolNode164", + "SolNode401", + "SolNode405", + "SolNode56", + "SolNode402", + "SolNode408", + "SolNode4", + "SolNode181", + "SolNode406", + "SolNode162", + "SolNode72", + "SolNode407", + "SolNode177", + "SolNode404", + "SolNode400", + "SolNode409", + "SolNode43", + "SolNode166", + "SolNode172", + "SolNode412", + "SolNode187", + "SolNode38", + "SolNode175", + "SolNode81", + "SolNode48", + "SolNode410", + "SolNode153", + "SolNode173" + ], + "VoidT5": ["SolNode747", "SolNode743", "SolNode742", "SolNode744", "SolNode745", "SolNode748", "SolNode746", "SolNode741"], + "VoidT6": ["SolNode717", "SolNode309", "SolNode718", "SolNode232", "SolNode230", "SolNode310"] +} diff --git a/static/fixed_responses/worldState/worldState.json b/static/fixed_responses/worldState/worldState.json index 73d48ce1..f973d69a 100644 --- a/static/fixed_responses/worldState/worldState.json +++ b/static/fixed_responses/worldState/worldState.json @@ -327,195 +327,6 @@ "Nodes": [] } ], - "ActiveMissions": [ - { - "_id": { "$oid": "663a7509d93367863785932d" }, - "Region": 15, - "Seed": 80795, - "Activation": { "$date": { "$numberLong": "1715107081517" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode400", - "MissionType": "MT_EXTERMINATION", - "Modifier": "VoidT3", - "Hard": true - }, - { - "_id": { "$oid": "663a75f959a5964cadb39879" }, - "Region": 19, - "Seed": 32067, - "Activation": { "$date": { "$numberLong": "1715107321237" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode747", - "MissionType": "MT_INTEL", - "Modifier": "VoidT5", - "Hard": true - }, - { - "_id": { "$oid": "663a779d3e347839ff301814" }, - "Region": 7, - "Seed": 51739, - "Activation": { "$date": { "$numberLong": "1715107741454" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode64", - "MissionType": "MT_TERRITORY", - "Modifier": "VoidT3" - }, - { - "_id": { "$oid": "663a77d916c199f4644ee67d" }, - "Region": 17, - "Seed": 61179, - "Activation": { "$date": { "$numberLong": "1715107801647" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode718", - "MissionType": "MT_ALCHEMY", - "Modifier": "VoidT6" - }, - { - "_id": { "$oid": "663a78c98a609b49b8410726" }, - "Region": 3, - "Seed": 9520, - "Activation": { "$date": { "$numberLong": "1715108041501" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode79", - "MissionType": "MT_INTEL", - "Modifier": "VoidT1", - "Hard": true - }, - { - "_id": { "$oid": "663a7df15eeabaac79b0a061" }, - "Region": 6, - "Seed": 48861, - "Activation": { "$date": { "$numberLong": "1715109361974" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode67", - "MissionType": "MT_INTEL", - "Modifier": "VoidT2", - "Hard": true - }, - { - "_id": { "$oid": "663a7df25eeabaac79b0a062" }, - "Region": 5, - "Seed": 13550, - "Activation": { "$date": { "$numberLong": "1715109361974" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode10", - "MissionType": "MT_SABOTAGE", - "Modifier": "VoidT2", - "Hard": true - }, - { - "_id": { "$oid": "663a83cdec0d5181435f1324" }, - "Region": 19, - "Seed": 39392, - "Activation": { "$date": { "$numberLong": "1715110861506" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode742", - "MissionType": "MT_DEFENSE", - "Modifier": "VoidT5" - }, - { - "_id": { "$oid": "663a83cdec0d5181435f1325" }, - "Region": 19, - "Seed": 88668, - "Activation": { "$date": { "$numberLong": "1715110861506" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode743", - "MissionType": "MT_MOBILE_DEFENSE", - "Modifier": "VoidT5" - }, - { - "_id": { "$oid": "663a83cdec0d5181435f1326" }, - "Region": 19, - "Seed": 73823, - "Activation": { "$date": { "$numberLong": "1715110861506" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode741", - "MissionType": "MT_ASSAULT", - "Modifier": "VoidT5" - }, - { - "_id": { "$oid": "663a878d23d1514873170466" }, - "Region": 9, - "Seed": 88696, - "Activation": { "$date": { "$numberLong": "1715111821951" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode4", - "MissionType": "MT_EXTERMINATION", - "Modifier": "VoidT4", - "Hard": true - }, - { - "_id": { "$oid": "663a887d4903098c10992fe6" }, - "Region": 6, - "Seed": 66337, - "Activation": { "$date": { "$numberLong": "1715112061729" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode18", - "MissionType": "MT_TERRITORY", - "Modifier": "VoidT2" - }, - { - "_id": { "$oid": "663a887d4903098c10992fe7" }, - "Region": 10, - "Seed": 5135, - "Activation": { "$date": { "$numberLong": "1715112061729" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode149", - "MissionType": "MT_DEFENSE", - "Modifier": "VoidT2" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d3" }, - "Region": 15, - "Seed": 32180, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode408", - "MissionType": "MT_DEFENSE", - "Modifier": "VoidT4" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d4" }, - "Region": 12, - "Seed": 22521, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode181", - "MissionType": "MT_EXTERMINATION", - "Modifier": "VoidT4" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d5" }, - "Region": 2, - "Seed": 28500, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode128", - "MissionType": "MT_EXTERMINATION", - "Modifier": "VoidT1" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d6" }, - "Region": 3, - "Seed": 24747, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode26", - "MissionType": "MT_DEFENSE", - "Modifier": "VoidT1" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d7" }, - "Region": 17, - "Seed": 63914, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode717", - "MissionType": "MT_SURVIVAL", - "Modifier": "VoidT6", - "Hard": true - } - ], "NodeOverrides": [ { "_id": { "$oid": "549b18e9b029cef5991d6aec" }, "Node": "EuropaHUB", "Hide": true }, { "_id": { "$oid": "54a1737aeb658f6cbccf70ff" }, "Node": "ErisHUB", "Hide": true },