Compare commits

...

47 Commits

Author SHA1 Message Date
8520650018 fix: don't push SORTIE_MODIFIER_HAZARD_RADIATION unconditionally 2025-05-03 16:06:33 +02:00
b958c108f9 chore: fix typo 2025-05-03 15:30:32 +02:00
8ae5fcfad0 fix: login failure on u25 & u26 (#1967)
also updated the setGuildMotd response for the old UI before LongMOTD

Reviewed-on: OpenWF/SpaceNinjaServer#1967
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-02 22:14:44 -07:00
468efed71c fix: handle tileset-specific sortie modifiers (#1958)
Closes #1956

Reviewed-on: OpenWF/SpaceNinjaServer#1958
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-02 15:07:00 -07:00
4926b2f2be fix: only refresh rewardSeed at EOM (#1957)
Fixes #1953

Reviewed-on: OpenWF/SpaceNinjaServer#1957
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-02 15:06:46 -07:00
562ddd513f chore: update docker stuff (#1961)
Some Docker stuff I updated ~~but keeping WIP for now, until I know whether this breaks or not, if someone could test it for me. Will close the PR if it doesn't, cuz if I cannot even run it on my machine (Docker only crashing on my end in general), then its pointless for me to mess with it.~~

Reviewed-on: OpenWF/SpaceNinjaServer#1961
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-05-02 15:06:36 -07:00
35d5c01203 fix: login failure on u29 heart of deimos and below (#1959)
Reviewed-on: OpenWF/SpaceNinjaServer#1959
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-02 04:03:42 -07:00
8f8bc5b364 fix: don't set G3/Zanuka death marks by default (#1950)
These should only be set to true after completing an invasion for the enemy faction. Re #1097

Reviewed-on: OpenWF/SpaceNinjaServer#1950
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 13:55:01 -07:00
a8227ce54c feat: cure vasca virus (#1949)
Closes #1946

Reviewed-on: OpenWF/SpaceNinjaServer#1949
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 13:54:50 -07:00
ec9dc2aa5f fix: multiply standing cost by purchase quantity (#1948)
Closes #1945

Reviewed-on: OpenWF/SpaceNinjaServer#1948
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 13:54:27 -07:00
f7906c91e3 fix: ignore purchaseQuantity for webui add items (#1944)
Closes #1942

Reviewed-on: OpenWF/SpaceNinjaServer#1944
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 13:54:04 -07:00
159598979d fix: don't duplicate level key credits reward (#1940)
Closes #1939

Reviewed-on: OpenWF/SpaceNinjaServer#1940
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 13:53:40 -07:00
4983417201 chore: update certificate (#1937)
This is good for *.faketls.com until March 2026.

Reviewed-on: OpenWF/SpaceNinjaServer#1937
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 13:53:28 -07:00
9b652f5c3c fix: spoof nemesis to avoid script errors in older versions (#1936)
Reviewed-on: OpenWF/SpaceNinjaServer#1936
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 13:53:10 -07:00
19b04533df fix: omit void fissures for U35.1 (#1935)
This version also has a script error even tho it should know most of the new deimos nodes...

Reviewed-on: OpenWF/SpaceNinjaServer#1935
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 13:52:43 -07:00
cddd4bdf5c fix: filter sortie armor/shields modifier based on mission faction (#1934)
Closes #1931

Reviewed-on: OpenWF/SpaceNinjaServer#1934
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 13:51:31 -07:00
12b6e5d16e fix: login failure on U31 the new war & U30.5 sisters of parvos (#1943)
Reviewed-on: OpenWF/SpaceNinjaServer#1943
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-01 05:23:25 -07:00
8eefd67d79 chore: fix typo 2025-05-01 13:09:45 +02:00
c4b2248df5 fix: login failure on U31.5 angels of the zariman (#1933)
Reviewed-on: OpenWF/SpaceNinjaServer#1933
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-30 23:50:02 -07:00
2c3043f40e fix: login failure on U32 veilbreaker (#1932)
Reviewed-on: OpenWF/SpaceNinjaServer#1932
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-30 15:45:42 -07:00
7d02906656 fix: better handling of assassination missions in sorties (#1930)
Closes #1918

Reviewed-on: OpenWF/SpaceNinjaServer#1930
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-30 13:28:34 -07:00
ed54e00a03 fix: compatibility with echoes of duviri (#1928)
Reviewed-on: OpenWF/SpaceNinjaServer#1928
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-30 13:28:24 -07:00
3d6c880c96 feat: handle client setting InfestationDate on equipment (#1927)
Closes #1919

Reviewed-on: OpenWF/SpaceNinjaServer#1927
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-30 13:28:16 -07:00
660768b53b fix: handle DuviriInfo being absent from inventory (#1926)
Closes #1917

Reviewed-on: OpenWF/SpaceNinjaServer#1926
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-30 13:28:01 -07:00
3de68e51d5 fix: properly set Harvestable & DeathSquadable fields (#1925)
Closes #1916

Reviewed-on: OpenWF/SpaceNinjaServer#1925
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-30 13:27:47 -07:00
c06abded11 fix: always multiply acquired gear quantity by purchaseQuantity (#1924)
Closes #1915

Reviewed-on: OpenWF/SpaceNinjaServer#1924
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-30 13:27:36 -07:00
9468768947 fix: weapon seed's low dword being sign extended (#1914)
JavaScript's semantics here are incredibly stupid, but basically if the initial DWORD's high WORD's MSB is true, the number would become negative after the shift left by 16. Then when ORing it with the highDword, the initial DWORD would be sign-extended to a QWORD, meaning the high DWORD would become all 1s, basically cancelling out the entire OR operation.

Reviewed-on: OpenWF/SpaceNinjaServer#1914
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 12:28:01 -07:00
0af7f41201 fix: unset LibraryPersonalTarget after completing it (#1913)
Reviewed-on: OpenWF/SpaceNinjaServer#1913
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 12:27:47 -07:00
de1e2a25f2 fix(webui): ensure that all requests using authz revalidate it (#1911)
Closes #1907

Reviewed-on: OpenWF/SpaceNinjaServer#1911
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 12:27:38 -07:00
1cf7b41d3f chore: note that random element functions could return undefined (#1910)
We should be explicit about the fact that we expect the arrays to not be empty.

Reviewed-on: OpenWF/SpaceNinjaServer#1910
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 12:27:25 -07:00
ab9cc685eb fix: exclude capture as a mission type for sorties (#1909)
Closes #1865

Reviewed-on: OpenWF/SpaceNinjaServer#1909
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 02:05:18 -07:00
743b784754 chore(webui): use plural form of "Moa", just to stay consistent with the other categories (#1905)
Reviewed-on: OpenWF/SpaceNinjaServer#1905
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-28 14:01:35 -07:00
5df533a7fb chore: auto-generate "daily special" for fish vendors (#1902)
Trying to go a bit more towards an "auto-generate by default" approach, with manual overrides where needed.

Reviewed-on: OpenWF/SpaceNinjaServer#1902
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:01:17 -07:00
9417aa3c84 fix: only consider market-listed blueprints for login reward (#1900)
Closes #1882

Reviewed-on: OpenWF/SpaceNinjaServer#1900
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:01:02 -07:00
a1872e2b07 chore: simplify getInnateDamageTag (#1899)
Reviewed-on: OpenWF/SpaceNinjaServer#1899
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:00:51 -07:00
9042e85355 feat: infested lich rewards (#1898)
Closes #1884

Reviewed-on: OpenWF/SpaceNinjaServer#1898
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:00:38 -07:00
66ee550ccd feat: refresh duviri seed when mood changes (#1895)
Closes #1887

Reviewed-on: OpenWF/SpaceNinjaServer#1895
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:00:22 -07:00
7a295a86ec fix: handle boosters in store item utilities (#1894)
e.g. `/Lotus/Types/StoreItems/Boosters/AffinityBoosterStoreItem`

Reviewed-on: OpenWF/SpaceNinjaServer#1894
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:00:06 -07:00
88d00eaaa1 feat: weaken nemesis (#1893)
Closes #1885

Reviewed-on: OpenWF/SpaceNinjaServer#1893
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 13:59:54 -07:00
1e8f2fc766 chore: comment out mixed fields in inventory (#1892)
If they are needed in the future, they schould be properly schema'd.

Reviewed-on: OpenWF/SpaceNinjaServer#1892
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 13:58:39 -07:00
0d842ade90 chore(webui): update German translation (#1904)
Reviewed-on: OpenWF/SpaceNinjaServer#1904
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-28 05:14:25 -07:00
4e3a2e17ee chore: removing unnecessary entries in allScans.json (#1903)
Reviewed-on: OpenWF/SpaceNinjaServer#1903
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-28 03:36:39 -07:00
61864b2be1 chore(webui): update to Spanish translation (#1901)
Reviewed-on: OpenWF/SpaceNinjaServer#1901
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-04-27 19:39:49 -07:00
45748fa8be fix: import failing for LotusCustomization from live (#1891)
Reviewed-on: OpenWF/SpaceNinjaServer#1891
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-27 14:20:52 -07:00
afec59e8a6 feat: skipClanKeyCrafting cheat (#1883)
Closes #1843

Reviewed-on: OpenWF/SpaceNinjaServer#1883
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-27 12:38:55 -07:00
ee1a49f5f2 feat: handle NemesisKillConvert at missionInventoryUpdate (#1880)
Closes #1848

Reviewed-on: OpenWF/SpaceNinjaServer#1880
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-27 12:38:48 -07:00
9e94083875 feat: handle KubrowPetEggs in missionInventoryUpdate (#1876)
Closes #1866

Reviewed-on: OpenWF/SpaceNinjaServer#1876
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-27 12:36:00 -07:00
57 changed files with 1414 additions and 957 deletions

View File

@ -5,17 +5,40 @@ ENV APP_MY_ADDRESS=localhost
ENV APP_HTTP_PORT=80 ENV APP_HTTP_PORT=80
ENV APP_HTTPS_PORT=443 ENV APP_HTTPS_PORT=443
ENV APP_AUTO_CREATE_ACCOUNT=true ENV APP_AUTO_CREATE_ACCOUNT=true
ENV APP_SKIP_STORY_MODE_CHOICE=true ENV APP_SKIP_TUTORIAL=false
ENV APP_SKIP_TUTORIAL=true ENV APP_SKIP_ALL_DIALOGUE=false
ENV APP_SKIP_ALL_DIALOGUE=true ENV APP_UNLOCK_ALL_SCANS=false
ENV APP_UNLOCK_ALL_SCANS=true ENV APP_UNLOCK_ALL_MISSIONS=false
ENV APP_UNLOCK_ALL_MISSIONS=true ENV APP_INFINITE_CREDITS=false
ENV APP_INFINITE_RESOURCES=true ENV APP_INFINITE_PLATINUM=false
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true ENV APP_INFINITE_ENDO=false
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true ENV APP_INFINITE_REGAL_AYA=false
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=true ENV APP_INFINITE_HELMINTH_MATERIALS=false
ENV APP_UNLOCK_ALL_SKINS=true ENV APP_DONT_SUBTRACT_CONSUMABLES=false
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=true 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_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 ENV APP_SPOOF_MASTERY_RANK=-1
RUN apk add --no-cache bash sed wget jq RUN apk add --no-cache bash sed wget jq

View File

@ -38,6 +38,7 @@
"noKimCooldowns": false, "noKimCooldowns": false,
"instantResourceExtractorDrones": false, "instantResourceExtractorDrones": false,
"noResourceExtractorDronesDamage": false, "noResourceExtractorDronesDamage": false,
"skipClanKeyCrafting": false,
"noDojoRoomBuildStage": false, "noDojoRoomBuildStage": false,
"noDecoBuildStage": false, "noDecoBuildStage": false,
"fastDojoRoomDestruction": false, "fastDojoRoomDestruction": false,

View File

@ -12,19 +12,40 @@ services:
# APP_HTTP_PORT: 80 # APP_HTTP_PORT: 80
# APP_HTTPS_PORT: 443 # APP_HTTPS_PORT: 443
# APP_AUTO_CREATE_ACCOUNT: true # APP_AUTO_CREATE_ACCOUNT: true
# APP_SKIP_STORY_MODE_CHOICE: true # APP_SKIP_TUTORIAL: false
# APP_SKIP_TUTORIAL: true # APP_SKIP_ALL_DIALOGUE: false
# APP_SKIP_ALL_DIALOGUE: true # APP_UNLOCK_ALL_SCANS: false
# APP_UNLOCK_ALL_SCANS: true # APP_UNLOCK_ALL_MISSIONS: false
# APP_UNLOCK_ALL_MISSIONS: true # APP_INFINITE_CREDITS: false
# APP_UNLOCK_ALL_QUESTS: true # APP_INFINITE_PLATINUM: false
# APP_COMPLETE_ALL_QUESTS: true # APP_INFINITE_ENDO: false
# APP_INFINITE_RESOURCES: true # APP_INFINITE_REGAL_AYA: false
# APP_UNLOCK_ALL_SHIP_FEATURES: true # APP_INFINITE_HELMINTH_MATERIALS: false
# APP_UNLOCK_ALL_SHIP_DECORATIONS: true # APP_DONT_SUBTRACT_CONSUMABLES: false
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: true # APP_UNLOCK_ALL_SHIP_FEATURES: false
# APP_UNLOCK_ALL_SKINS: true # APP_UNLOCK_ALL_SHIP_DECORATIONS: false
# APP_UNIVERSAL_POLARITY_EVERYWHERE: true # 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_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 # APP_SPOOF_MASTERY_RANK: -1
volumes: volumes:
- ./docker-data/static:/app/static/data - ./docker-data/static:/app/static/data

View File

@ -19,5 +19,6 @@ do
mv config.tmp config.json mv config.tmp config.json
done done
npm install npm i --omit=dev
exec npm run dev npm run build
exec npm run start

8
package-lock.json generated
View File

@ -18,7 +18,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.58", "warframe-public-export-plus": "^0.5.59",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
@ -3789,9 +3789,9 @@
} }
}, },
"node_modules/warframe-public-export-plus": { "node_modules/warframe-public-export-plus": {
"version": "0.5.58", "version": "0.5.59",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.58.tgz", "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.59.tgz",
"integrity": "sha512-2G3tKcoblUl7S3Rkk5k/qH+VGZBUmU2QjtIrEO/Bt6UlgO83s648elkNdDKOLBKXnxIsa194nVwz+ci1K86sXg==" "integrity": "sha512-/SUCVjngVDBz6gahz7CdVLywtHLODL6O5nmNtQcxFDUwrUGnF1lETcG8/UO+WLeGxBVAy4BDPbq+9ZWlYZM4uQ=="
}, },
"node_modules/warframe-riven-info": { "node_modules/warframe-riven-info": {
"version": "0.1.2", "version": "0.1.2",

View File

@ -25,7 +25,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.58", "warframe-public-export-plus": "^0.5.59",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"

View File

@ -17,7 +17,7 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
ItemCount: -1 ItemCount: -1
} }
]); ]);
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]); const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]); const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex = const upgradeIndex =
inventory.Upgrades.push({ inventory.Upgrades.push({

View File

@ -28,7 +28,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
}); });
const rawRivenType = getRandomRawRivenType(); const rawRivenType = getRandomRawRivenType();
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]); const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType])!;
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]); const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex = const upgradeIndex =

View File

@ -133,7 +133,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
if (recipe.secretIngredientAction != "SIA_UNBRAND") { if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,
...(await addItem(inventory, recipe.resultType, recipe.num, false)) ...(await addItem(
inventory,
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
))
}; };
} }
await inventory.save(); await inventory.save();

View File

@ -1,8 +1,14 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild, GuildMember } from "@/src/models/guildModel"; import { Guild, GuildMember } from "@/src/models/guildModel";
import { Account } from "@/src/models/loginModel"; import { Account } from "@/src/models/loginModel";
import { deleteGuild, getGuildClient, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService"; import {
import { addRecipes, combineInventoryChanges, getInventory } from "@/src/services/inventoryService"; deleteGuild,
getGuildClient,
giveClanKey,
hasGuildPermission,
removeDojoKeyItems
} from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService"; import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes"; import { GuildPermission } from "@/src/types/guildTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
@ -41,14 +47,7 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
// Update inventory of new member // Update inventory of new member
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes"); const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
inventory.GuildId = new Types.ObjectId(req.query.clanId as string); inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
const recipeChanges = [ giveClanKey(inventory, inventoryChanges);
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
];
addRecipes(inventory, recipeChanges);
combineInventoryChanges(inventoryChanges, { Recipes: recipeChanges });
await inventory.save(); await inventory.save();
const guild = (await Guild.findById(req.query.clanId as string))!; const guild = (await Guild.findById(req.query.clanId as string))!;
@ -96,14 +95,9 @@ export const confirmGuildInvitationPostController: RequestHandler = async (req,
await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 }); await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
// Update inventory of new member // Update inventory of new member
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId Recipes"); const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes");
inventory.GuildId = new Types.ObjectId(req.query.clanId as string); inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
addRecipes(inventory, [ giveClanKey(inventory);
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]);
await inventory.save(); await inventory.save();
// Add join to clan log // Add join to clan log

View File

@ -2,8 +2,9 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild, GuildMember } from "@/src/models/guildModel"; import { Guild, GuildMember } from "@/src/models/guildModel";
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService"; import { createUniqueClanName, getGuildClient, giveClanKey } from "@/src/services/guildService";
import { addRecipes, getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
export const createGuildController: RequestHandler = async (req, res) => { export const createGuildController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -26,26 +27,15 @@ export const createGuildController: RequestHandler = async (req, res) => {
rank: 0 rank: 0
}); });
const inventory = await getInventory(accountId, "GuildId Recipes"); const inventory = await getInventory(accountId, "GuildId LevelKeys Recipes");
inventory.GuildId = guild._id; inventory.GuildId = guild._id;
addRecipes(inventory, [ const inventoryChanges: IInventoryChanges = {};
{ giveClanKey(inventory, inventoryChanges);
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]);
await inventory.save(); await inventory.save();
res.json({ res.json({
...(await getGuildClient(guild, accountId)), ...(await getGuildClient(guild, accountId)),
InventoryChanges: { InventoryChanges: inventoryChanges
Recipes: [
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]
}
}); });
}; };

View File

@ -18,6 +18,7 @@ export const getAllianceController: RequestHandler = async (req, res) => {
res.end(); res.end();
}; };
// POST request since U27
/*interface IGetAllianceRequest { /*interface IGetAllianceRequest {
memberCount: number; memberCount: number;
clanLeaderName: string; clanLeaderName: string;

View File

@ -11,8 +11,10 @@ import {
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService"; import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService"; import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportFlavour, ExportGear } from "warframe-public-export-plus"; import { ExportFlavour } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
import { IOid } from "@/src/types/commonTypes";
export const inboxController: RequestHandler = async (req, res) => { export const inboxController: RequestHandler = async (req, res) => {
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query; const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
@ -27,10 +29,10 @@ export const inboxController: RequestHandler = async (req, res) => {
return; return;
} }
await deleteMessageRead(deleteId as string); await deleteMessageRead(parseOid(deleteId as string));
res.status(200).end(); res.status(200).end();
} else if (messageId) { } else if (messageId) {
const message = await getMessage(messageId as string); const message = await getMessage(parseOid(messageId as string));
message.r = true; message.r = true;
await message.save(); await message.save();
@ -48,8 +50,8 @@ export const inboxController: RequestHandler = async (req, res) => {
await addItems( await addItems(
inventory, inventory,
attachmentItems.map(attItem => ({ attachmentItems.map(attItem => ({
ItemType: attItem, ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1 ItemCount: 1
})), })),
inventoryChanges inventoryChanges
); );
@ -99,7 +101,7 @@ export const inboxController: RequestHandler = async (req, res) => {
await createNewEventMessages(req); await createNewEventMessages(req);
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 }); const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
const latestClientMessage = messages.find(m => m._id.toString() === latestClientMessageId); const latestClientMessage = messages.find(m => m._id.toString() === parseOid(latestClientMessageId as string));
if (!latestClientMessage) { if (!latestClientMessage) {
logger.debug(`this should only happen after DeleteAllRead `); logger.debug(`this should only happen after DeleteAllRead `);
@ -122,3 +124,11 @@ export const inboxController: RequestHandler = async (req, res) => {
res.json({ Inbox: inbox }); res.json({ Inbox: inbox });
} }
}; };
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
const parseOid = (oid: string): string => {
if (oid[0] == "{") {
return (JSON.parse(oid) as IOid).$oid;
}
return oid;
};

View File

@ -1,5 +1,5 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountForRequest } from "@/src/services/loginService";
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import allDialogue from "@/static/fixed_responses/allDialogue.json"; import allDialogue from "@/static/fixed_responses/allDialogue.json";
@ -18,15 +18,18 @@ import {
addMiscItems, addMiscItems,
allDailyAffiliationKeys, allDailyAffiliationKeys,
cleanupInventory, cleanupInventory,
createLibraryDailyTask createLibraryDailyTask,
generateRewardSeed
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { catBreadHash } from "@/src/helpers/stringHelpers"; import { catBreadHash } from "@/src/helpers/stringHelpers";
import { Types } from "mongoose";
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
export const inventoryController: RequestHandler = async (request, response) => { export const inventoryController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request); const account = await getAccountForRequest(request);
const inventory = await Inventory.findOne({ accountOwnerId: accountId }); const inventory = await Inventory.findOne({ accountOwnerId: account._id });
if (!inventory) { if (!inventory) {
response.status(400).json({ error: "inventory was undefined" }); response.status(400).json({ error: "inventory was undefined" });
@ -87,7 +90,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
cleanupInventory(inventory); cleanupInventory(inventory);
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000); inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
await inventory.save(); //await inventory.save();
} }
if ( if (
@ -96,15 +99,36 @@ export const inventoryController: RequestHandler = async (request, response) =>
new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown
) { ) {
handleSubsumeCompletion(inventory); handleSubsumeCompletion(inventory);
await inventory.save(); //await inventory.save();
} }
response.json(await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query)); if (inventory.LastInventorySync) {
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
if (lastSyncDuviriMood != currentDuviriMood) {
logger.debug(`refreshing duviri seed`);
if (!inventory.DuviriInfo) {
inventory.DuviriInfo = {
Seed: generateRewardSeed(),
NumCompletions: 0
};
} else {
inventory.DuviriInfo.Seed = generateRewardSeed();
}
}
}
inventory.LastInventorySync = new Types.ObjectId();
await inventory.save();
response.json(
await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query, account.BuildLabel)
);
}; };
export const getInventoryResponse = async ( export const getInventoryResponse = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
xpBasedLevelCapDisabled: boolean xpBasedLevelCapDisabled: boolean,
buildLabel: string | undefined
): Promise<IInventoryClient> => { ): Promise<IInventoryClient> => {
const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>( const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>(
"LoadOutPresets" "LoadOutPresets"
@ -274,11 +298,20 @@ export const getInventoryResponse = async (
} }
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage. // Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
//inventoryResponse.LastInventorySync = toOid(new Types.ObjectId()); inventoryResponse.LastInventorySync = undefined;
// Set 2FA enabled so trading post can be used // Set 2FA enabled so trading post can be used
inventoryResponse.HWIDProtectEnabled = true; inventoryResponse.HWIDProtectEnabled = true;
// Fix nemesis for older versions
if (
inventoryResponse.Nemesis &&
buildLabel &&
!isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)
) {
inventoryResponse.Nemesis = undefined;
}
return inventoryResponse; return inventoryResponse;
}; };

View File

@ -7,6 +7,7 @@ import { Account } from "@/src/models/loginModel";
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService"; import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes"; import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { version_compare } from "@/src/services/worldStateService";
export const loginController: RequestHandler = async (request, response) => { export const loginController: RequestHandler = async (request, response) => {
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
@ -46,7 +47,8 @@ export const loginController: RequestHandler = async (request, response) => {
ForceLogoutVersion: 0, ForceLogoutVersion: 0,
ConsentNeeded: false, ConsentNeeded: false,
TrackedSettings: [], TrackedSettings: [],
Nonce: nonce Nonce: nonce,
BuildLabel: buildLabel
}); });
logger.debug("created new account"); logger.debug("created new account");
response.json(createLoginResponse(myAddress, newAccount, buildLabel)); response.json(createLoginResponse(myAddress, newAccount, buildLabel));
@ -87,6 +89,7 @@ export const loginController: RequestHandler = async (request, response) => {
account.ClientType = loginRequest.ClientType; account.ClientType = loginRequest.ClientType;
account.Nonce = nonce; account.Nonce = nonce;
account.CountryCode = loginRequest.lang.toUpperCase(); account.CountryCode = loginRequest.lang.toUpperCase();
account.BuildLabel = buildLabel;
} }
await account.save(); await account.save();
@ -94,25 +97,40 @@ export const loginController: RequestHandler = async (request, response) => {
}; };
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => { const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
return { const resp: ILoginResponse = {
id: account.id, id: account.id,
DisplayName: account.DisplayName, DisplayName: account.DisplayName,
CountryCode: account.CountryCode, CountryCode: account.CountryCode,
ClientType: account.ClientType,
CrossPlatformAllowed: account.CrossPlatformAllowed,
ForceLogoutVersion: account.ForceLogoutVersion,
AmazonAuthToken: account.AmazonAuthToken, AmazonAuthToken: account.AmazonAuthToken,
AmazonRefreshToken: account.AmazonRefreshToken, AmazonRefreshToken: account.AmazonRefreshToken,
ConsentNeeded: account.ConsentNeeded, ConsentNeeded: account.ConsentNeeded,
TrackedSettings: account.TrackedSettings, TrackedSettings: account.TrackedSettings,
Nonce: account.Nonce, Nonce: account.Nonce,
Groups: [],
IRC: config.myIrcAddresses ?? [myAddress], IRC: config.myIrcAddresses ?? [myAddress],
platformCDNs: [`https://${myAddress}/`],
HUB: `https://${myAddress}/api/`,
NRS: config.NRS, NRS: config.NRS,
DTLS: 99, BuildLabel: buildLabel
BuildLabel: buildLabel,
MatchmakingBuildId: buildConfig.matchmakingBuildId
}; };
if (version_compare(buildLabel, "2019.08.29.20.01") >= 0) {
// U25.7 and up
resp.ForceLogoutVersion = account.ForceLogoutVersion;
}
if (version_compare(buildLabel, "2019.10.31.22.42") >= 0) {
// U26 and up
resp.Groups = [];
}
if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) {
resp.DTLS = 99;
}
if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
resp.ClientType = account.ClientType;
}
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
resp.HUB = `https://${myAddress}/api/`;
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
}
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
resp.platformCDNs = [`https://${myAddress}/`];
}
return resp;
}; };

View File

@ -1,6 +1,6 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountForRequest } from "@/src/services/loginService";
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes"; import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService"; import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
import { generateRewardSeed, getInventory } from "@/src/services/inventoryService"; import { generateRewardSeed, getInventory } from "@/src/services/inventoryService";
@ -49,11 +49,11 @@ import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
*/ */
//move credit calc in here, return MissionRewards: [] if no reward info //move credit calc in here, return MissionRewards: [] if no reward info
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => { export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
const accountId = await getAccountIdForRequest(req); const account = await getAccountForRequest(req);
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString()); const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
logger.debug("mission report:", missionReport); logger.debug("mission report:", missionReport);
const inventory = await getInventory(accountId); const inventory = await getInventory(account._id.toString());
const firstCompletion = missionReport.SortieId const firstCompletion = missionReport.SortieId
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1 ? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
: false; : false;
@ -63,9 +63,11 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
missionReport.MissionStatus !== "GS_SUCCESS" && missionReport.MissionStatus !== "GS_SUCCESS" &&
!(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId) !(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
) { ) {
inventory.RewardSeed = generateRewardSeed(); if (missionReport.EndOfMatchUpload) {
inventory.RewardSeed = generateRewardSeed();
}
await inventory.save(); await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true); const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
res.json({ res.json({
InventoryJson: JSON.stringify(inventoryResponse), InventoryJson: JSON.stringify(inventoryResponse),
MissionRewards: [] MissionRewards: []
@ -82,9 +84,11 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
ConquestCompletedMissionsCount ConquestCompletedMissionsCount
} = await addMissionRewards(inventory, missionReport, firstCompletion); } = await addMissionRewards(inventory, missionReport, firstCompletion);
inventory.RewardSeed = generateRewardSeed(); if (missionReport.EndOfMatchUpload) {
inventory.RewardSeed = generateRewardSeed();
}
await inventory.save(); await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true); const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
//TODO: figure out when to send inventory. it is needed for many cases. //TODO: figure out when to send inventory. it is needed for many cases.
res.json({ res.json({

View File

@ -141,7 +141,7 @@ const getModularWeaponSale = (
getItemType: (parts: string[]) => string getItemType: (parts: string[]) => string
): IModularWeaponSaleInfo => { ): IModularWeaponSaleInfo => {
const rng = new CRng(day); const rng = new CRng(day);
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])); const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
let partsCost = 0; let partsCost = 0;
for (const part of parts) { for (const part of parts) {
partsCost += ExportWeapons[part].premiumPrice!; partsCost += ExportWeapons[part].premiumPrice!;

View File

@ -2,8 +2,12 @@ import {
consumeModCharge, consumeModCharge,
encodeNemesisGuess, encodeNemesisGuess,
getInfNodes, getInfNodes,
getKnifeUpgrade,
getNemesisPasscode, getNemesisPasscode,
IKnifeResponse getNemesisPasscodeModTypes,
getWeaponsForManifest,
IKnifeResponse,
showdownNodes
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
@ -14,6 +18,8 @@ import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { import {
IInnateDamageFingerprint, IInnateDamageFingerprint,
IInventoryClient,
INemesisClient,
InventorySlot, InventorySlot,
IUpgradeClient, IUpgradeClient,
IWeaponSkinClient, IWeaponSkinClient,
@ -99,50 +105,45 @@ export const nemesisController: RequestHandler = async (req, res) => {
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3) encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
); );
// Increase antivirus // Increase antivirus if correct antivirus mod is installed
let antivirusGain = 5;
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
const response: IKnifeResponse = {}; const response: IKnifeResponse = {};
for (const upgrade of body.knife!.AttachedUpgrades) { if (result1 == 0 || result2 == 0 || result3 == 0) {
switch (upgrade.ItemType) { let antivirusGain = 5;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod": const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
antivirusGain += 10; const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
break; const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod": for (const upgrade of body.knife!.AttachedUpgrades) {
antivirusGain += 10; switch (upgrade.ItemType) {
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
break; antivirusGain += 10;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
antivirusGain += 15; break;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
break; antivirusGain += 10;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
antivirusGain += 15; break;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
break; antivirusGain += 15;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod": consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
antivirusGain += 10; break;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
break; antivirusGain += 15;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
}
} }
inventory.Nemesis!.HenchmenKilled += antivirusGain;
} }
inventory.Nemesis!.HenchmenKilled += antivirusGain;
if (inventory.Nemesis!.HenchmenKilled >= 100) { if (inventory.Nemesis!.HenchmenKilled >= 100) {
inventory.Nemesis!.HenchmenKilled = 100; inventory.Nemesis!.HenchmenKilled = 100;
inventory.Nemesis!.InfNodes = [
{
Node: "CrewBattleNode559",
Influence: 1
}
];
inventory.Nemesis!.Weakened = true;
} else {
inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
} }
inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
await inventory.save(); await inventory.save();
res.json(response); res.json(response);
@ -170,18 +171,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
let weaponIdx = -1; let weaponIdx = -1;
if (body.target.Faction != "FC_INFESTATION") { if (body.target.Faction != "FC_INFESTATION") {
let weapons: readonly string[]; const weapons = getWeaponsForManifest(body.target.manifest);
if (body.target.manifest == "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix") {
weapons = kuvaLichVersionSixWeapons;
} else if (
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour" ||
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree"
) {
weapons = corpusVersionThreeWeapons;
} else {
throw new Error(`unknown nemesis manifest: ${body.target.manifest}`);
}
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1); const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
weaponIdx = initialWeaponIdx; weaponIdx = initialWeaponIdx;
do { do {
@ -223,6 +213,38 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({ res.json({
target: inventory.toJSON().Nemesis target: inventory.toJSON().Nemesis
}); });
} else if ((req.query.mode as string) == "w") {
const inventory = await getInventory(
accountId,
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
);
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
inventory.Nemesis!.InfNodes = [
{
Node: showdownNodes[inventory.Nemesis!.Faction],
Influence: 1
}
];
inventory.Nemesis!.Weakened = true;
const response: IKnifeResponse & { target: INemesisClient } = {
target: inventory.toJSON<IInventoryClient>().Nemesis!
};
// Consume charge of the correct requiem mod(s)
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
const modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!);
for (const modType of modTypes) {
const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType);
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
}
await inventory.save();
res.json(response);
} else { } else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`); logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`); throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`);
@ -274,48 +296,19 @@ interface INemesisRequiemRequest {
guess: number; // grn/crp: 4 bits | coda: 3x 4 bits guess: number; // grn/crp: 4 bits | coda: 3x 4 bits
position: number; // grn/crp: 0-2 | coda: 0 position: number; // grn/crp: 0-2 | coda: 0
// knife field provided for coda only // knife field provided for coda only
knife?: { knife?: IKnife;
Item: IEquipmentClient;
Skins: IWeaponSkinClient[];
ModSlot: number;
CustSlot: number;
AttachedUpgrades: IUpgradeClient[];
HiddenWhenHolstered: boolean;
};
} }
const kuvaLichVersionSixWeapons = [ // interface INemesisWeakenRequest {
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon", // target: INemesisClient;
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak", // knife: IKnife;
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon", // }
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
"/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
"/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
];
const corpusVersionThreeWeapons = [ interface IKnife {
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher", Item: IEquipmentClient;
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor", Skins: IWeaponSkinClient[];
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle", ModSlot: number;
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra", CustSlot: number;
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron", AttachedUpgrades: IUpgradeClient[];
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron", HiddenWhenHolstered: boolean;
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol", }
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
];

View File

@ -2,6 +2,7 @@ import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
import { hasGuildPermissionEx } from "@/src/services/guildService"; import { hasGuildPermissionEx } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService"; import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { version_compare } from "@/src/services/worldStateService";
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes"; import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
@ -55,5 +56,9 @@ export const setGuildMotdController: RequestHandler = async (req, res) => {
await guild.save(); await guild.save();
} }
res.json({ IsLongMOTD, MOTD }); if (!account.BuildLabel || version_compare(account.BuildLabel, "2020.03.24.20.24") > 0) {
res.json({ IsLongMOTD, MOTD });
} else {
res.send(MOTD).end();
}
}; };

View File

@ -7,7 +7,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
const requests = req.body as IAddItemRequest[]; const requests = req.body as IAddItemRequest[];
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
for (const request of requests) { for (const request of requests) {
await addItem(inventory, request.ItemType, request.ItemCount, true); await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, undefined, true);
} }
await inventory.save(); await inventory.save();
res.end(); res.end();

View File

@ -1,4 +1,4 @@
import { addGearExpByCategory, getInventory } from "@/src/services/inventoryService"; import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
@ -20,7 +20,7 @@ export const addXpController: RequestHandler = async (req, res) => {
} }
} }
} }
addGearExpByCategory(inventory, gear, category as TEquipmentKey); applyClientEquipmentUpdates(inventory, gear, category as TEquipmentKey);
} }
await inventory.save(); await inventory.save();
res.end(); res.end();

View File

@ -10,6 +10,10 @@ export const toMongoDate = (date: Date): IMongoDate => {
return { $date: { $numberLong: date.getTime().toString() } }; return { $date: { $numberLong: date.getTime().toString() } };
}; };
export const fromMongoDate = (date: IMongoDate): Date => {
return new Date(parseInt(date.$date.$numberLong));
};
export const kubrowWeights: Record<TRarity, number> = { export const kubrowWeights: Record<TRarity, number> = {
COMMON: 6, COMMON: 6,
UNCOMMON: 4, UNCOMMON: 4,

View File

@ -1,12 +1,14 @@
import { ExportRegions } from "warframe-public-export-plus"; import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInfNode, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { SRng } from "@/src/services/rngService"; import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { IOid } from "../types/commonTypes"; import { IOid } from "../types/commonTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { addMods } from "../services/inventoryService"; import { addMods, generateRewardSeed } from "../services/inventoryService";
import { isArchwingMission } from "../services/worldStateService"; import { isArchwingMission, version_compare } from "../services/worldStateService";
import { fromStoreItem, toStoreItem } from "../services/itemDataService";
import { createMessage } from "../services/inboxService";
export const getInfNodes = (faction: string, rank: number): IInfNode[] => { export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
const infNodes = []; const infNodes = [];
@ -38,17 +40,59 @@ const systemIndexes: Record<string, number[]> = {
FC_INFESTATION: [23] FC_INFESTATION: [23]
}; };
export const showdownNodes: Record<string, string> = {
FC_GRINEER: "CrewBattleNode557",
FC_CORPUS: "CrewBattleNode558",
FC_INFESTATION: "CrewBattleNode559"
};
// Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis. // Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => { export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => {
const rng = new SRng(nemesis.fp); const rng = new SRng(nemesis.fp);
const passcode = [rng.randomInt(0, 7)]; const choices = [0, 1, 2, 3, 5, 6, 7];
let choiceIndex = rng.randomInt(0, choices.length - 1);
const passcode = [choices[choiceIndex]];
if (nemesis.Faction != "FC_INFESTATION") { if (nemesis.Faction != "FC_INFESTATION") {
passcode.push(rng.randomInt(0, 7)); choices.splice(choiceIndex, 1);
passcode.push(rng.randomInt(0, 7)); choiceIndex = rng.randomInt(0, choices.length - 1);
passcode.push(choices[choiceIndex]);
choices.splice(choiceIndex, 1);
choiceIndex = rng.randomInt(0, choices.length - 1);
passcode.push(choices[choiceIndex]);
} }
return passcode; return passcode;
}; };
const reqiuemMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod"
];
const antivirusMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
];
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: string }): string[] => {
const passcode = getNemesisPasscode(nemesis);
return nemesis.Faction == "FC_INFESTATION"
? passcode.map(i => antivirusMods[i])
: passcode.map(i => reqiuemMods[i]);
};
export const encodeNemesisGuess = ( export const encodeNemesisGuess = (
symbol1: number, symbol1: number,
result1: number, result1: number,
@ -79,6 +123,31 @@ export interface IKnifeResponse {
HasKnife?: boolean; HasKnife?: boolean;
} }
export const getKnifeUpgrade = (
inventory: TInventoryDatabaseDocument,
dataknifeUpgrades: string[],
type: string
): { ItemId: IOid; ItemType: string } => {
if (dataknifeUpgrades.indexOf(type) != -1) {
return {
ItemId: { $oid: "000000000000000000000000" },
ItemType: type
};
}
for (const upgradeId of dataknifeUpgrades) {
if (upgradeId.length == 24) {
const upgrade = inventory.Upgrades.id(upgradeId);
if (upgrade && upgrade.ItemType == type) {
return {
ItemId: { $oid: upgradeId },
ItemType: type
};
}
}
}
throw new Error(`${type} does not seem to be installed on parazon?!`);
};
export const consumeModCharge = ( export const consumeModCharge = (
response: IKnifeResponse, response: IKnifeResponse,
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
@ -129,3 +198,205 @@ export const consumeModCharge = (
response.UpgradeNew.push(true); response.UpgradeNew.push(true);
} }
}; };
const kuvaLichVersionSixWeapons = [
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
"/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
"/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
];
const corpusVersionThreeWeapons = [
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
];
export const getWeaponsForManifest = (manifest: string): readonly string[] => {
switch (manifest) {
case "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix": // >= 35.6.0
return kuvaLichVersionSixWeapons;
case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree": // >= 35.6.0
case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour": // >= 37.0.0
return corpusVersionThreeWeapons;
}
throw new Error(`unknown nemesis manifest: ${manifest}`);
};
export const isNemesisCompatibleWithVersion = (
nemesis: { manifest: string; Faction: string },
buildLabel: string
): boolean => {
// Anything below 35.6.0 is not going to be okay given our set of supported manifests.
if (version_compare(buildLabel, "2024.05.15.11.07") < 0) {
return false;
}
if (nemesis.Faction == "FC_INFESTATION") {
// Anything below 38.5.0 isn't gonna like an infested lich.
if (version_compare(buildLabel, "2025.03.18.16.07") < 0) {
return false;
}
} else if (nemesis.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour") {
// Anything below 37.0.0 isn't gonna know version 4, but version 3 is identical in terms of weapon choices, so we can spoof it to that.
if (version_compare(buildLabel, "2024.10.01.11.03") < 0) {
nemesis.manifest = "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree";
}
}
return true;
};
export const getInnateDamageTag = (
KillingSuit: string
):
| "InnateElectricityDamage"
| "InnateFreezeDamage"
| "InnateHeatDamage"
| "InnateImpactDamage"
| "InnateMagDamage"
| "InnateRadDamage"
| "InnateToxinDamage" => {
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
};
// TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
export const getInnateDamageValue = (fp: bigint): number => {
const rng = new SRng(fp);
rng.randomFloat(); // used for the weapon index
const WeaponUpgradeValueAttenuationExponent = 2.25;
let value = Math.pow(rng.randomFloat(), WeaponUpgradeValueAttenuationExponent);
if (value >= 0.941428) {
value = 1;
}
return Math.trunc(value * 0x40000000);
};
export const getKillTokenRewardCount = (fp: bigint): number => {
const rng = new SRng(fp);
return rng.randomInt(10, 15);
};
// /Lotus/Types/Enemies/InfestedLich/InfestedLichRewardManifest
const infestedLichRotA = [
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDJRomHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDJRomInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDrillbitHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDrillbitInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyHarddriveHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyHarddriveInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyPacketHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyPacketInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyZekeHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyZekeInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandBillboardPosterA", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandBillboardPosterB", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandDespairPoster", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandGridPoster", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandHuddlePoster", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandJumpPoster", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLimoPoster", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLookingDownPosterDay", probability: 0.046 },
{
type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLookingDownPosterNight",
probability: 0.045
},
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandSillyPoster", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandWhiteBluePoster", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandWhitePinkPoster", probability: 0.045 }
];
const infestedLichRotB = [
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraA", probability: 0.072 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraB", probability: 0.071 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraC", probability: 0.072 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraD", probability: 0.071 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraE", probability: 0.072 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraF", probability: 0.071 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraG", probability: 0.071 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraH", probability: 0.072 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DanceDJRomHype", probability: 0.071 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DancePacketWindmillShuffle", probability: 0.072 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DanceHarddrivePony", probability: 0.071 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DanceDrillbitCrisscross", probability: 0.072 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DanceZekeCanthavethis", probability: 0.071 },
{ type: "/Lotus/StoreItems/Types/Items/PhotoBooth/PhotoboothTileRJLasXStadiumBossArena", probability: 0.071 }
];
export const getInfestedLichItemRewards = (fp: bigint): string[] => {
const rng = new SRng(fp);
const rotAReward = getRewardAtPercentage(infestedLichRotA, rng.randomFloat())!.type;
rng.randomFloat(); // unused afaict
const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type;
return [rotAReward, rotBReward];
};
export const sendCodaFinishedMessage = async (
inventory: TInventoryDatabaseDocument,
fp: bigint = generateRewardSeed(),
name: string = "ZEKE_BEATWOMAN_TM.1999",
killed: boolean = true
): Promise<void> => {
const att: string[] = [];
// First vanquish/convert gives a sigil
const sigil = killed
? "/Lotus/Upgrades/Skins/Sigils/InfLichVanquishedSigil"
: "/Lotus/Upgrades/Skins/Sigils/InfLichConvertedSigil";
if (!inventory.WeaponSkins.find(x => x.ItemType == sigil)) {
att.push(toStoreItem(sigil));
}
const [rotAReward, rotBReward] = getInfestedLichItemRewards(fp);
att.push(fromStoreItem(rotAReward));
att.push(fromStoreItem(rotBReward));
let countedAtt: ITypeCount[] | undefined;
if (killed) {
countedAtt = [
{
ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
ItemCount: getKillTokenRewardCount(fp)
}
];
}
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/VanquishBandMsgBody",
arg: [
{
Key: "LICH_NAME",
Tag: name
}
],
att: att,
countedAtt: countedAtt,
sub: "/Lotus/Language/Inbox/VanquishBandMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
highPriority: true
}
]);
};

View File

@ -31,7 +31,7 @@ export interface IFingerprintStat {
} }
export const createVeiledRivenFingerprint = (meta: IUpgrade): IVeiledRivenFingerprint => { export const createVeiledRivenFingerprint = (meta: IUpgrade): IVeiledRivenFingerprint => {
const challenge = getRandomElement(meta.availableChallenges!); const challenge = getRandomElement(meta.availableChallenges!)!;
const fingerprintChallenge: IRivenChallenge = { const fingerprintChallenge: IRivenChallenge = {
Type: challenge.fullName, Type: challenge.fullName,
Progress: 0, Progress: 0,
@ -54,11 +54,11 @@ export const createVeiledRivenFingerprint = (meta: IUpgrade): IVeiledRivenFinger
export const createUnveiledRivenFingerprint = (meta: IUpgrade): IUnveiledRivenFingerprint => { export const createUnveiledRivenFingerprint = (meta: IUpgrade): IUnveiledRivenFingerprint => {
const fingerprint: IUnveiledRivenFingerprint = { const fingerprint: IUnveiledRivenFingerprint = {
compat: getRandomElement(meta.compatibleItems!), compat: getRandomElement(meta.compatibleItems!)!,
lim: 0, lim: 0,
lvl: 0, lvl: 0,
lvlReq: getRandomInt(8, 16), lvlReq: getRandomInt(8, 16),
pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"]), pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"])!,
buffs: [], buffs: [],
curses: [] curses: []
}; };
@ -81,7 +81,7 @@ export const randomiseRivenStats = (meta: IUpgrade, fingerprint: IUnveiledRivenF
if (Math.random() < 0.5) { if (Math.random() < 0.5) {
const entry = getRandomElement( const entry = getRandomElement(
meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag)) meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag))
); )!;
fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) }); fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
} }
}; };

View File

@ -391,8 +391,8 @@ MailboxSchema.set("toJSON", {
const DuviriInfoSchema = new Schema<IDuviriInfo>( const DuviriInfoSchema = new Schema<IDuviriInfo>(
{ {
Seed: BigInt, Seed: { type: BigInt, required: true },
NumCompletions: { type: Number, default: 0 } NumCompletions: { type: Number, required: true }
}, },
{ {
_id: false, _id: false,
@ -781,9 +781,25 @@ const loreFragmentScansSchema = new Schema<ILoreFragmentScan>(
{ _id: false } { _id: false }
); );
const lotusCustomizationSchema = new Schema<ILotusCustomization>().add(ItemConfigSchema).add({ // const lotusCustomizationSchema = new Schema<ILotusCustomization>().add(ItemConfigSchema).add({
Persona: String // Persona: String
}); // });
// Laxer schema for cleanupInventory
const lotusCustomizationSchema = new Schema<ILotusCustomization>(
{
Skins: [String],
pricol: colorSchema,
attcol: Schema.Types.Mixed,
sigcol: Schema.Types.Mixed,
eyecol: Schema.Types.Mixed,
facial: Schema.Types.Mixed,
cloth: Schema.Types.Mixed,
syancol: Schema.Types.Mixed,
Persona: String
},
{ _id: false }
);
const evolutionProgressSchema = new Schema<IEvolutionProgress>( const evolutionProgressSchema = new Schema<IEvolutionProgress>(
{ {
@ -1039,6 +1055,8 @@ const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
{ {
ItemType: String, ItemType: String,
CompletionDate: Date, CompletionDate: Date,
TargetItemId: String,
TargetFingerprint: String,
LongGuns: { type: [EquipmentSchema], default: undefined }, LongGuns: { type: [EquipmentSchema], default: undefined },
Pistols: { type: [EquipmentSchema], default: undefined }, Pistols: { type: [EquipmentSchema], default: undefined },
Melee: { type: [EquipmentSchema], default: undefined }, Melee: { type: [EquipmentSchema], default: undefined },
@ -1260,11 +1278,11 @@ const nemesisSchema = new Schema<INemesisDatabase>(
PrevOwners: Number, PrevOwners: Number,
SecondInCommand: Boolean, SecondInCommand: Boolean,
Weakened: Boolean, Weakened: Boolean,
InfNodes: [infNodeSchema], InfNodes: { type: [infNodeSchema], default: undefined },
HenchmenKilled: Number, HenchmenKilled: Number,
HintProgress: Number, HintProgress: Number,
Hints: [Number], Hints: { type: [Number], default: undefined },
GuessHistory: [Number], GuessHistory: { type: [Number], default: undefined },
MissionCount: Number, MissionCount: Number,
LastEnc: Number LastEnc: Number
}, },
@ -1381,7 +1399,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//How many Gift do you have left*(gift spends the trade) //How many Gift do you have left*(gift spends the trade)
GiftsRemaining: { type: Number, default: 8 }, GiftsRemaining: { type: Number, default: 8 },
//Curent trade info Giving or Getting items //Curent trade info Giving or Getting items
PendingTrades: [Schema.Types.Mixed], //PendingTrades: [Schema.Types.Mixed],
//Syndicate currently being pledged to. //Syndicate currently being pledged to.
SupportedSyndicate: String, SupportedSyndicate: String,
@ -1431,7 +1449,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
KubrowPetEggs: [kubrowPetEggSchema], KubrowPetEggs: [kubrowPetEggSchema],
//Prints Cat(3 Prints)\Kubrow(2 Prints) Pets //Prints Cat(3 Prints)\Kubrow(2 Prints) Pets
KubrowPetPrints: [Schema.Types.Mixed], //KubrowPetPrints: [Schema.Types.Mixed],
//Item for EquippedGear example:Scaner,LoadoutTechSummon etc //Item for EquippedGear example:Scaner,LoadoutTechSummon etc
Consumables: [typeCountSchema], Consumables: [typeCountSchema],
@ -1477,7 +1495,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//item like DojoKey or Boss missions key //item like DojoKey or Boss missions key
LevelKeys: [typeCountSchema], LevelKeys: [typeCountSchema],
//Active quests //Active quests
Quests: [Schema.Types.Mixed], //Quests: [Schema.Types.Mixed],
//Cosmetics like profile glyphs\Kavasa Prime Kubrow Collar\Game Theme etc //Cosmetics like profile glyphs\Kavasa Prime Kubrow Collar\Game Theme etc
FlavourItems: [FlavourItemSchema], FlavourItems: [FlavourItemSchema],
@ -1516,7 +1534,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
TauntHistory: { type: [tauntSchema], default: undefined }, TauntHistory: { type: [tauntSchema], default: undefined },
//noShow2FA,VisitPrimeVault etc //noShow2FA,VisitPrimeVault etc
WebFlags: Schema.Types.Mixed, //WebFlags: Schema.Types.Mixed,
//Id CompletedAlerts //Id CompletedAlerts
CompletedAlerts: [String], CompletedAlerts: [String],
@ -1536,7 +1554,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//the color your clan requests like Items/Research/DojoColors/DojoColorPlainsB //the color your clan requests like Items/Research/DojoColors/DojoColorPlainsB
ActiveDojoColorResearch: String, ActiveDojoColorResearch: String,
SentientSpawnChanceBoosters: Schema.Types.Mixed, //SentientSpawnChanceBoosters: Schema.Types.Mixed,
QualifyingInvasions: [invasionProgressSchema], QualifyingInvasions: [invasionProgressSchema],
FactionScores: [Number], FactionScores: [Number],
@ -1571,10 +1589,10 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
// open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable // open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable
DiscoveredMarkers: [discoveredMarkerSchema], DiscoveredMarkers: [discoveredMarkerSchema],
//Open location mission like "JobId" + "StageCompletions" //Open location mission like "JobId" + "StageCompletions"
CompletedJobs: [Schema.Types.Mixed], //CompletedJobs: [Schema.Types.Mixed],
//Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258, //Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258,
PersonalGoalProgress: [Schema.Types.Mixed], //PersonalGoalProgress: [Schema.Types.Mixed],
//Setting interface Style //Setting interface Style
ThemeStyle: String, ThemeStyle: String,
@ -1604,13 +1622,13 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
LibraryActiveDailyTaskInfo: libraryDailyTaskInfoSchema, LibraryActiveDailyTaskInfo: libraryDailyTaskInfoSchema,
//https://warframe.fandom.com/wiki/Invasion //https://warframe.fandom.com/wiki/Invasion
InvasionChainProgress: [Schema.Types.Mixed], //InvasionChainProgress: [Schema.Types.Mixed],
//CorpusLich or GrineerLich //CorpusLich or GrineerLich
NemesisAbandonedRewards: { type: [String], default: [] }, NemesisAbandonedRewards: { type: [String], default: [] },
Nemesis: nemesisSchema, Nemesis: nemesisSchema,
NemesisHistory: [Schema.Types.Mixed], NemesisHistory: { type: [nemesisSchema], default: undefined },
LastNemesisAllySpawnTime: Schema.Types.Mixed, //LastNemesisAllySpawnTime: Schema.Types.Mixed,
//TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social) //TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social)
Settings: settingsSchema, Settings: settingsSchema,
@ -1624,7 +1642,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
PlayerSkills: { type: playerSkillsSchema, default: {} }, PlayerSkills: { type: playerSkillsSchema, default: {} },
//TradeBannedUntil data //TradeBannedUntil data
TradeBannedUntil: Schema.Types.Mixed, //TradeBannedUntil: Schema.Types.Mixed,
//https://warframe.fandom.com/wiki/Helminth //https://warframe.fandom.com/wiki/Helminth
InfestedFoundry: infestedFoundrySchema, InfestedFoundry: infestedFoundrySchema,
@ -1644,23 +1662,24 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//Unknown and system //Unknown and system
DuviriInfo: DuviriInfoSchema, DuviriInfo: DuviriInfoSchema,
LastInventorySync: Schema.Types.ObjectId,
Mailbox: MailboxSchema, Mailbox: MailboxSchema,
HandlerPoints: Number, HandlerPoints: Number,
ChallengesFixVersion: { type: Number, default: 6 }, ChallengesFixVersion: { type: Number, default: 6 },
PlayedParkourTutorial: Boolean, PlayedParkourTutorial: Boolean,
ActiveLandscapeTraps: [Schema.Types.Mixed], //ActiveLandscapeTraps: [Schema.Types.Mixed],
RepVotes: [Schema.Types.Mixed], //RepVotes: [Schema.Types.Mixed],
LeagueTickets: [Schema.Types.Mixed], //LeagueTickets: [Schema.Types.Mixed],
HasContributedToDojo: Boolean, HasContributedToDojo: Boolean,
HWIDProtectEnabled: Boolean, HWIDProtectEnabled: Boolean,
LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" }, LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" },
CurrentLoadOutIds: [oidSchema], CurrentLoadOutIds: [oidSchema],
RandomUpgradesIdentified: Number, RandomUpgradesIdentified: Number,
BountyScore: Number, BountyScore: Number,
ChallengeInstanceStates: [Schema.Types.Mixed], //ChallengeInstanceStates: [Schema.Types.Mixed],
RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined }, RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined },
Robotics: [Schema.Types.Mixed], //Robotics: [Schema.Types.Mixed],
UsedDailyDeals: [Schema.Types.Mixed], //UsedDailyDeals: [Schema.Types.Mixed],
CollectibleSeries: { type: [collectibleEntrySchema], default: undefined }, CollectibleSeries: { type: [collectibleEntrySchema], default: undefined },
HasResetAccount: { type: Boolean, default: false }, HasResetAccount: { type: Boolean, default: false },
@ -1741,6 +1760,9 @@ inventorySchema.set("toJSON", {
sn: inventoryDatabase.LockedWeaponGroup.sn ? toOid(inventoryDatabase.LockedWeaponGroup.sn) : undefined sn: inventoryDatabase.LockedWeaponGroup.sn ? toOid(inventoryDatabase.LockedWeaponGroup.sn) : undefined
}; };
} }
if (inventoryDatabase.LastInventorySync) {
inventoryResponse.LastInventorySync = toOid(inventoryDatabase.LastInventorySync);
}
} }
}); });

View File

@ -20,6 +20,7 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
ConsentNeeded: { type: Boolean, required: true }, ConsentNeeded: { type: Boolean, required: true },
TrackedSettings: { type: [String], default: [] }, TrackedSettings: { type: [String], default: [] },
Nonce: { type: Number, default: 0 }, Nonce: { type: Number, default: 0 },
BuildLabel: String,
Dropped: Boolean, Dropped: Boolean,
LatestEventMessageDate: { type: Date, default: 0 }, LatestEventMessageDate: { type: Date, default: 0 },
LastLoginRewardDate: { type: Number, default: 0 }, LastLoginRewardDate: { type: Number, default: 0 },

View File

@ -170,6 +170,7 @@ apiRouter.get("/deleteSession.php", deleteSessionController);
apiRouter.get("/divvyAllianceVault.php", divvyAllianceVaultController); apiRouter.get("/divvyAllianceVault.php", divvyAllianceVaultController);
apiRouter.get("/dojo", dojoController); apiRouter.get("/dojo", dojoController);
apiRouter.get("/drones.php", dronesController); apiRouter.get("/drones.php", dronesController);
apiRouter.get("/getAlliance.php", getAllianceController);
apiRouter.get("/getDailyDealStockLevels.php", getDailyDealStockLevelsController); apiRouter.get("/getDailyDealStockLevels.php", getDailyDealStockLevelsController);
apiRouter.get("/getFriends.php", getFriendsController); apiRouter.get("/getFriends.php", getFriendsController);
apiRouter.get("/getGuild.php", getGuildController); apiRouter.get("/getGuild.php", getGuildController);
@ -308,6 +309,7 @@ apiRouter.post("/trainingResult.php", trainingResultController);
apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController); apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController);
apiRouter.post("/updateAlignment.php", updateAlignmentController); apiRouter.post("/updateAlignment.php", updateAlignmentController);
apiRouter.post("/updateChallengeProgress.php", updateChallengeProgressController); apiRouter.post("/updateChallengeProgress.php", updateChallengeProgressController);
apiRouter.post("/updateInventory.php", missionInventoryUpdateController); // U26 and below
apiRouter.post("/updateNodeIntros.php", genericUpdateController); apiRouter.post("/updateNodeIntros.php", genericUpdateController);
apiRouter.post("/updateQuest.php", updateQuestController); apiRouter.post("/updateQuest.php", updateQuestController);
apiRouter.post("/updateSession.php", updateSessionPostController); apiRouter.post("/updateSession.php", updateSessionPostController);

View File

@ -44,6 +44,7 @@ interface IConfig {
noKimCooldowns?: boolean; noKimCooldowns?: boolean;
instantResourceExtractorDrones?: boolean; instantResourceExtractorDrones?: boolean;
noResourceExtractorDronesDamage?: boolean; noResourceExtractorDronesDamage?: boolean;
skipClanKeyCrafting?: boolean;
noDojoRoomBuildStage?: boolean; noDojoRoomBuildStage?: boolean;
noDojoDecoBuildStage?: boolean; noDojoDecoBuildStage?: boolean;
fastDojoRoomDestruction?: boolean; fastDojoRoomDestruction?: boolean;

View File

@ -1,6 +1,6 @@
import { Request } from "express"; import { Request } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService"; import { addLevelKeys, addRecipes, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
import { Alliance, AllianceMember, Guild, GuildAd, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel"; import { Alliance, AllianceMember, Guild, GuildAd, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { import {
@ -657,6 +657,32 @@ export const checkClanAscensionHasRequiredContributors = async (guild: TGuildDat
} }
}; };
export const giveClanKey = (inventory: TInventoryDatabaseDocument, inventoryChanges?: IInventoryChanges): void => {
if (config.skipClanKeyCrafting) {
const levelKeyChanges = [
{
ItemType: "/Lotus/Types/Keys/DojoKey",
ItemCount: 1
}
];
addLevelKeys(inventory, levelKeyChanges);
if (inventoryChanges) {
combineInventoryChanges(inventoryChanges, { LevelKeys: levelKeyChanges });
}
} else {
const recipeChanges = [
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
];
addRecipes(inventory, recipeChanges);
if (inventoryChanges) {
combineInventoryChanges(inventoryChanges, { Recipes: recipeChanges });
}
}
};
export const removeDojoKeyItems = (inventory: TInventoryDatabaseDocument): IInventoryChanges => { export const removeDojoKeyItems = (inventory: TInventoryDatabaseDocument): IInventoryChanges => {
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};

View File

@ -2,6 +2,7 @@ import { Types } from "mongoose";
import { import {
IEquipmentClient, IEquipmentClient,
IEquipmentDatabase, IEquipmentDatabase,
IItemConfig,
IOperatorConfigClient, IOperatorConfigClient,
IOperatorConfigDatabase IOperatorConfigDatabase
} from "../types/inventoryTypes/commonInventoryTypes"; } from "../types/inventoryTypes/commonInventoryTypes";
@ -174,6 +175,20 @@ const convertNemesis = (client: INemesisClient): INemesisDatabase => {
}; };
}; };
// Empty objects from live may have been encoded as empty arrays because of PHP.
const convertItemConfig = <T extends IItemConfig>(client: T): T => {
return {
...client,
pricol: Array.isArray(client.pricol) ? {} : client.pricol,
attcol: Array.isArray(client.attcol) ? {} : client.attcol,
sigcol: Array.isArray(client.sigcol) ? {} : client.sigcol,
eyecol: Array.isArray(client.eyecol) ? {} : client.eyecol,
facial: Array.isArray(client.facial) ? {} : client.facial,
cloth: Array.isArray(client.cloth) ? {} : client.cloth,
syancol: Array.isArray(client.syancol) ? {} : client.syancol
};
};
export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<IInventoryClient>): void => { export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<IInventoryClient>): void => {
for (const key of equipmentKeys) { for (const key of equipmentKeys) {
if (client[key] !== undefined) { if (client[key] !== undefined) {
@ -352,7 +367,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
db.PlayerSkills = client.PlayerSkills; db.PlayerSkills = client.PlayerSkills;
} }
if (client.LotusCustomization !== undefined) { if (client.LotusCustomization !== undefined) {
db.LotusCustomization = client.LotusCustomization; db.LotusCustomization = convertItemConfig(client.LotusCustomization);
} }
if (client.CollectibleSeries !== undefined) { if (client.CollectibleSeries !== undefined) {
db.CollectibleSeries = client.CollectibleSeries; db.CollectibleSeries = client.CollectibleSeries;

View File

@ -26,7 +26,9 @@ import {
Status, Status,
IKubrowPetDetailsDatabase, IKubrowPetDetailsDatabase,
ITraits, ITraits,
ICalendarProgress ICalendarProgress,
INemesisWeaponTargetFingerprint,
INemesisPetTargetFingerprint
} from "@/src/types/inventoryTypes/inventoryTypes"; } from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate"; import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes"; import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
@ -67,6 +69,7 @@ import {
import { createShip } from "./shipService"; import { createShip } from "./shipService";
import { import {
catbrowDetails, catbrowDetails,
fromMongoDate,
kubrowDetails, kubrowDetails,
kubrowFurPatternsWeights, kubrowFurPatternsWeights,
kubrowWeights, kubrowWeights,
@ -79,6 +82,7 @@ import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from ".
import { createMessage } from "./inboxService"; import { createMessage } from "./inboxService";
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper"; import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
import { getWorldState } from "./worldStateService"; import { getWorldState } from "./worldStateService";
import { getInnateDamageTag, getInnateDamageValue } from "../helpers/nemesisHelpers";
export const createInventory = async ( export const createInventory = async (
accountOwnerId: Types.ObjectId, accountOwnerId: Types.ObjectId,
@ -327,7 +331,9 @@ export const addItem = async (
typeName: string, typeName: string,
quantity: number = 1, quantity: number = 1,
premiumPurchase: boolean = false, premiumPurchase: boolean = false,
seed?: bigint seed?: bigint,
targetFingerprint?: string,
exactQuantity: boolean = false
): Promise<IInventoryChanges> => { ): Promise<IInventoryChanges> => {
// Bundles are technically StoreItems but a) they don't have a normal counterpart, and b) they are used in non-StoreItem contexts, e.g. email attachments. // Bundles are technically StoreItems but a) they don't have a normal counterpart, and b) they are used in non-StoreItem contexts, e.g. email attachments.
if (typeName in ExportBundles) { if (typeName in ExportBundles) {
@ -482,6 +488,12 @@ export const addItem = async (
}; };
} }
if (typeName in ExportGear) { if (typeName in ExportGear) {
// Multipling by purchase quantity for gear because:
// - The Saya's Vigil scanner message has it as a non-counted attachment.
// - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity.
if (!exactQuantity) {
quantity *= ExportGear[typeName].purchaseQuantity ?? 1;
}
const consumablesChanges = [ const consumablesChanges = [
{ {
ItemType: typeName, ItemType: typeName,
@ -530,6 +542,12 @@ export const addItem = async (
] ]
}); });
} }
if (targetFingerprint) {
const targetFingerprintObj = JSON.parse(targetFingerprint) as INemesisWeaponTargetFingerprint;
defaultOverwrites.UpgradeType = targetFingerprintObj.ItemType;
defaultOverwrites.UpgradeFingerprint = JSON.stringify(targetFingerprintObj.UpgradeFingerprint);
defaultOverwrites.ItemName = targetFingerprintObj.Name;
}
const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName, defaultOverwrites); const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName, defaultOverwrites);
if (weapon.additionalItems) { if (weapon.additionalItems) {
for (const item of weapon.additionalItems) { for (const item of weapon.additionalItems) {
@ -544,6 +562,27 @@ export const addItem = async (
premiumPurchase premiumPurchase
) )
}; };
} else if (targetFingerprint) {
// Sister's Hound
const targetFingerprintObj = JSON.parse(targetFingerprint) as INemesisPetTargetFingerprint;
const head = targetFingerprintObj.Parts[0];
const defaultOverwrites: Partial<IEquipmentDatabase> = {
ModularParts: targetFingerprintObj.Parts,
ItemName: targetFingerprintObj.Name,
Configs: applyDefaultUpgrades(inventory, ExportWeapons[head].defaultUpgrades)
};
const itemType = {
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
}[head] as string;
return {
...addEquipment(inventory, "MoaPets", itemType, defaultOverwrites),
...occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase)
};
} else { } else {
// Modular weapon parts // Modular weapon parts
const miscItemChanges = [ const miscItemChanges = [
@ -1440,21 +1479,20 @@ export const addEmailItem = async (
return inventoryChanges; return inventoryChanges;
}; };
//TODO: wrong id is not erroring export const applyClientEquipmentUpdates = (
export const addGearExpByCategory = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
gearArray: IEquipmentClient[], gearArray: IEquipmentClient[],
categoryName: TEquipmentKey categoryName: TEquipmentKey
): void => { ): void => {
const category = inventory[categoryName]; const category = inventory[categoryName];
gearArray.forEach(({ ItemId, XP }) => { gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
if (!XP) { const item = category.id(ItemId.$oid);
return; if (!item) {
throw new Error(`No item with id ${ItemId.$oid} in ${categoryName}`);
} }
const item = category.id(ItemId.$oid); if (XP) {
if (item) {
item.XP ??= 0; item.XP ??= 0;
item.XP += XP; item.XP += XP;
@ -1469,6 +1507,12 @@ export const addGearExpByCategory = (
}); });
} }
} }
if (InfestationDate) {
// 2147483647000 means cured, otherwise became infected
item.InfestationDate =
InfestationDate.$date.$numberLong == "2147483647000" ? new Date(0) : fromMongoDate(InfestationDate);
}
}); });
}; };
@ -1767,7 +1811,7 @@ export const addKeyChainItems = async (
}; };
export const createLibraryDailyTask = (): ILibraryDailyTaskInfo => { export const createLibraryDailyTask = (): ILibraryDailyTaskInfo => {
const enemyTypes = getRandomElement(libraryDailyTasks); const enemyTypes = getRandomElement(libraryDailyTasks)!;
const enemyAvatar = ExportEnemies.avatars[enemyTypes[0]]; const enemyAvatar = ExportEnemies.avatars[enemyTypes[0]];
const scansRequired = getRandomInt(2, 4); const scansRequired = getRandomInt(2, 4);
return { return {
@ -1816,6 +1860,25 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
logger.debug(`removing FreeFavorsEarned from LibrarySyndicate`); logger.debug(`removing FreeFavorsEarned from LibrarySyndicate`);
LibrarySyndicate.FreeFavorsEarned = undefined; LibrarySyndicate.FreeFavorsEarned = undefined;
} }
if (inventory.LotusCustomization) {
if (
Array.isArray(inventory.LotusCustomization.attcol) ||
Array.isArray(inventory.LotusCustomization.sigcol) ||
Array.isArray(inventory.LotusCustomization.eyecol) ||
Array.isArray(inventory.LotusCustomization.facial) ||
Array.isArray(inventory.LotusCustomization.cloth) ||
Array.isArray(inventory.LotusCustomization.syancol)
) {
logger.debug(`fixing empty objects represented as empty arrays in LotusCustomization`);
inventory.LotusCustomization.attcol = {};
inventory.LotusCustomization.sigcol = {};
inventory.LotusCustomization.eyecol = {};
inventory.LotusCustomization.facial = {};
inventory.LotusCustomization.cloth = {};
inventory.LotusCustomization.syancol = {};
}
}
}; };
export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICalendarProgress => { export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICalendarProgress => {
@ -1851,3 +1914,78 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
return inventory.CalendarProgress; return inventory.CalendarProgress;
}; };
export const giveNemesisWeaponRecipe = (
inventory: TInventoryDatabaseDocument,
weaponType: string,
nemesisName: string = "AGOR ROK",
weaponLoc?: string,
KillingSuit: string = "/Lotus/Powersuits/Ember/Ember",
fp: bigint = generateRewardSeed()
): void => {
if (!weaponLoc) {
weaponLoc = ExportWeapons[weaponType].name;
}
const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == weaponType)![0];
addRecipes(inventory, [
{
ItemType: recipeType,
ItemCount: 1
}
]);
inventory.PendingRecipes.push({
CompletionDate: new Date(),
ItemType: recipeType,
TargetFingerprint: JSON.stringify({
ItemType: "/Lotus/Weapons/Grineer/KuvaLich/Upgrades/InnateDamageRandomMod",
UpgradeFingerprint: {
compat: weaponType,
buffs: [
{
Tag: getInnateDamageTag(KillingSuit),
Value: getInnateDamageValue(fp)
}
]
},
Name: weaponLoc + "|" + nemesisName
} satisfies INemesisWeaponTargetFingerprint)
});
};
export const giveNemesisPetRecipe = (inventory: TInventoryDatabaseDocument, nemesisName: string = "AGOR ROK"): void => {
const head = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
])!;
const body = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyC"
])!;
const legs = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsC"
])!;
const tail = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailC"
])!;
const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == head)![0];
addRecipes(inventory, [
{
ItemType: recipeType,
ItemCount: 1
}
]);
inventory.PendingRecipes.push({
CompletionDate: new Date(),
ItemType: recipeType,
TargetFingerprint: JSON.stringify({
Parts: [head, body, legs, tail],
Name: "/Lotus/Language/Pets/ZanukaPetName|" + nemesisName
} satisfies INemesisPetTargetFingerprint)
});
};

View File

@ -17,6 +17,7 @@ import {
dict_uk, dict_uk,
dict_zh, dict_zh,
ExportArcanes, ExportArcanes,
ExportBoosters,
ExportCustoms, ExportCustoms,
ExportDrones, ExportDrones,
ExportGear, ExportGear,
@ -217,15 +218,30 @@ export const convertInboxMessage = (message: IInboxMessage): IMessage => {
}; };
export const isStoreItem = (type: string): boolean => { export const isStoreItem = (type: string): boolean => {
return type.startsWith("/Lotus/StoreItems/"); return type.startsWith("/Lotus/StoreItems/") || type in ExportBoosters;
}; };
export const toStoreItem = (type: string): string => { export const toStoreItem = (type: string): string => {
if (type.startsWith("/Lotus/Types/StoreItems/Boosters/")) {
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type);
if (boosterEntry) {
return boosterEntry[0];
}
throw new Error(`could not convert ${type} to a store item`);
}
return "/Lotus/StoreItems/" + type.substring("/Lotus/".length); return "/Lotus/StoreItems/" + type.substring("/Lotus/".length);
}; };
export const fromStoreItem = (type: string): string => { export const fromStoreItem = (type: string): string => {
return "/Lotus/" + type.substring("/Lotus/StoreItems/".length); if (type.startsWith("/Lotus/StoreItems/")) {
return "/Lotus/" + type.substring("/Lotus/StoreItems/".length);
}
if (type in ExportBoosters) {
return ExportBoosters[type].typeName;
}
throw new Error(`${type} is not a store item`);
}; };
export const getDefaultUpgrades = (parts: string[]): IDefaultUpgrade[] | undefined => { export const getDefaultUpgrades = (parts: string[]): IDefaultUpgrade[] | undefined => {

View File

@ -77,7 +77,6 @@ const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatab
const reward = rng.randomReward(randomRewards)!; const reward = rng.randomReward(randomRewards)!;
//const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!; //const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!;
if (reward.RewardType == "RT_RANDOM_RECIPE") { if (reward.RewardType == "RT_RANDOM_RECIPE") {
// Not very faithful implementation but roughly the same idea
const masteredItems = new Set(); const masteredItems = new Set();
for (const entry of inventory.XPInfo) { for (const entry of inventory.XPInfo) {
masteredItems.add(entry.ItemType); masteredItems.add(entry.ItemType);
@ -95,15 +94,15 @@ const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatab
} }
const eligibleRecipes: string[] = []; const eligibleRecipes: string[] = [];
for (const [uniqueName, recipe] of Object.entries(ExportRecipes)) { for (const [uniqueName, recipe] of Object.entries(ExportRecipes)) {
if (unmasteredItems.has(recipe.resultType)) { if (!recipe.excludeFromMarket && unmasteredItems.has(recipe.resultType)) {
eligibleRecipes.push(uniqueName); eligibleRecipes.push(uniqueName);
} }
} }
if (eligibleRecipes.length == 0) { if (eligibleRecipes.length == 0) {
// This account has all warframes and weapons already mastered (filthy cheater), need a different reward. // This account has all applicable warframes and weapons already mastered (filthy cheater), need a different reward.
return getRandomLoginReward(rng, day, inventory); return getRandomLoginReward(rng, day, inventory);
} }
reward.StoreItemType = toStoreItem(rng.randomElement(eligibleRecipes)); reward.StoreItemType = toStoreItem(rng.randomElement(eligibleRecipes)!);
} }
return { return {
//_id: toOid(new Types.ObjectId()), //_id: toOid(new Types.ObjectId()),

View File

@ -21,7 +21,6 @@ import {
addFocusXpIncreases, addFocusXpIncreases,
addFusionPoints, addFusionPoints,
addFusionTreasures, addFusionTreasures,
addGearExpByCategory,
addItem, addItem,
addLevelKeys, addLevelKeys,
addLoreFragmentScans, addLoreFragmentScans,
@ -32,9 +31,12 @@ import {
addShipDecorations, addShipDecorations,
addSkin, addSkin,
addStanding, addStanding,
applyClientEquipmentUpdates,
combineInventoryChanges, combineInventoryChanges,
generateRewardSeed, generateRewardSeed,
getCalendarProgress, getCalendarProgress,
giveNemesisPetRecipe,
giveNemesisWeaponRecipe,
updateCurrency, updateCurrency,
updateSyndicate updateSyndicate
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
@ -53,7 +55,7 @@ import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json"; import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json"; import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json"; import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
import { getInfNodes } from "@/src/helpers/nemesisHelpers"; import { getInfNodes, getWeaponsForManifest, sendCodaFinishedMessage } from "@/src/helpers/nemesisHelpers";
import { Loadout } from "../models/inventoryModels/loadoutModel"; import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService"; import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService";
@ -141,38 +143,6 @@ export const addMissionInventoryUpdates = async (
]); ]);
} }
} }
// Somewhat heuristically detect G3 capture:
// - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1365
// - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1694
// - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1724
if (
inventoryUpdates.MissionFailed &&
inventoryUpdates.MissionStatus == "GS_FAILURE" &&
inventoryUpdates.ObjectiveReached &&
!inventoryUpdates.LockedWeaponGroup &&
!inventory.LockedWeaponGroup &&
!inventoryUpdates.LevelKeyName
) {
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!;
const SuitId = new Types.ObjectId(config.s!.ItemId.$oid);
inventory.BrandedSuits ??= [];
if (!inventory.BrandedSuits.find(x => x.equals(SuitId))) {
inventory.BrandedSuits.push(SuitId);
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/BrandedMessage",
sub: "/Lotus/Language/G1Quests/BrandedTitle",
att: ["/Lotus/Types/Recipes/Components/BrandRemovalBlueprint"],
highPriority: true // TOVERIFY: I cannot find any content of this within the last 10 years so I can only assume that highPriority is set (it certainly would make sense), but I just don't know for sure that it is so on live.
}
]);
}
}
} }
if (inventoryUpdates.RewardInfo) { if (inventoryUpdates.RewardInfo) {
if (inventoryUpdates.RewardInfo.periodicMissionTag) { if (inventoryUpdates.RewardInfo.periodicMissionTag) {
@ -378,6 +348,7 @@ export const addMissionInventoryUpdates = async (
: 10) : 10)
) { ) {
progress.Completed = true; progress.Completed = true;
inventory.LibraryPersonalTarget = undefined;
} }
logger.debug(`synthesis of ${scan.EnemyType} added to personal target progress`); logger.debug(`synthesis of ${scan.EnemyType} added to personal target progress`);
synthesisIgnored = false; synthesisIgnored = false;
@ -513,6 +484,16 @@ export const addMissionInventoryUpdates = async (
} }
break; break;
} }
case "KubrowPetEggs": {
for (const egg of value) {
inventory.KubrowPetEggs ??= [];
inventory.KubrowPetEggs.push({
ItemType: egg.ItemType,
_id: new Types.ObjectId()
});
}
break;
}
case "DiscoveredMarkers": { case "DiscoveredMarkers": {
for (const clientMarker of value) { for (const clientMarker of value) {
const dbMarker = inventory.DiscoveredMarkers.find(x => x.tag == clientMarker.tag); const dbMarker = inventory.DiscoveredMarkers.find(x => x.tag == clientMarker.tag);
@ -524,6 +505,23 @@ export const addMissionInventoryUpdates = async (
} }
break; break;
} }
case "BrandedSuits": {
inventory.BrandedSuits ??= [];
if (!inventory.BrandedSuits.find(x => x.equals(value.$oid))) {
inventory.BrandedSuits.push(new Types.ObjectId(value.$oid));
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/BrandedMessage",
sub: "/Lotus/Language/G1Quests/BrandedTitle",
att: ["/Lotus/Types/Recipes/Components/BrandRemovalBlueprint"],
highPriority: true // TOVERIFY: I cannot find any content of this within the last 10 years so I can only assume that highPriority is set (it certainly would make sense), but I just don't know for sure that it is so on live.
}
]);
}
break;
}
case "LockedWeaponGroup": { case "LockedWeaponGroup": {
inventory.LockedWeaponGroup = { inventory.LockedWeaponGroup = {
s: new Types.ObjectId(value.s.$oid), s: new Types.ObjectId(value.s.$oid),
@ -532,12 +530,17 @@ export const addMissionInventoryUpdates = async (
m: value.m ? new Types.ObjectId(value.m.$oid) : undefined, m: value.m ? new Types.ObjectId(value.m.$oid) : undefined,
sn: value.sn ? new Types.ObjectId(value.sn.$oid) : undefined sn: value.sn ? new Types.ObjectId(value.sn.$oid) : undefined
}; };
inventory.Harvestable = false;
break; break;
} }
case "UnlockWeapons": { case "UnlockWeapons": {
inventory.LockedWeaponGroup = undefined; inventory.LockedWeaponGroup = undefined;
break; break;
} }
case "IncHarvester": {
inventory.Harvestable = true;
break;
}
case "CurrentLoadOutIds": { case "CurrentLoadOutIds": {
if (value.LoadOuts) { if (value.LoadOuts) {
const loadout = await Loadout.findOne({ loadoutOwnerId: inventory.accountOwnerId }); const loadout = await Loadout.findOne({ loadoutOwnerId: inventory.accountOwnerId });
@ -598,14 +601,67 @@ export const addMissionInventoryUpdates = async (
case "duviriCaveOffers": { case "duviriCaveOffers": {
// Duviri cave offers (generated with the duviri seed) change after completing one of its game modes (not when aborting). // Duviri cave offers (generated with the duviri seed) change after completing one of its game modes (not when aborting).
if (inventoryUpdates.MissionStatus != "GS_QUIT") { if (inventoryUpdates.MissionStatus != "GS_QUIT") {
inventory.DuviriInfo.Seed = generateRewardSeed(); inventory.DuviriInfo!.Seed = generateRewardSeed();
} }
break; break;
} }
case "NemesisKillConvert":
if (inventory.Nemesis) {
inventory.NemesisHistory ??= [];
inventory.NemesisHistory.push({
// Copy over all 'base' values
fp: inventory.Nemesis.fp,
d: inventory.Nemesis.d,
manifest: inventory.Nemesis.manifest,
KillingSuit: inventory.Nemesis.KillingSuit,
killingDamageType: inventory.Nemesis.killingDamageType,
ShoulderHelmet: inventory.Nemesis.ShoulderHelmet,
WeaponIdx: inventory.Nemesis.WeaponIdx,
AgentIdx: inventory.Nemesis.AgentIdx,
BirthNode: inventory.Nemesis.BirthNode,
Faction: inventory.Nemesis.Faction,
Rank: inventory.Nemesis.Rank,
Traded: inventory.Nemesis.Traded,
PrevOwners: inventory.Nemesis.PrevOwners,
SecondInCommand: inventory.Nemesis.SecondInCommand,
Weakened: inventory.Nemesis.Weakened,
// And set killed flag
k: value.killed
});
if (value.killed) {
if (
value.weaponLoc &&
inventory.Nemesis.Faction != "FC_INFESTATION" // weaponLoc is "/Lotus/Language/Weapons/DerelictCernosName" for these for some reason
) {
const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
inventory.Nemesis.WeaponIdx
];
giveNemesisWeaponRecipe(
inventory,
weaponType,
value.nemesisName,
value.weaponLoc,
inventory.Nemesis.KillingSuit,
inventory.Nemesis.fp
);
}
if (value.petLoc) {
giveNemesisPetRecipe(inventory);
}
}
// TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given?
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
await sendCodaFinishedMessage(inventory, inventory.Nemesis.fp, value.nemesisName, value.killed);
}
inventory.Nemesis = undefined;
}
break;
default: default:
// Equipment XP updates
if (equipmentKeys.includes(key as TEquipmentKey)) { if (equipmentKeys.includes(key as TEquipmentKey)) {
addGearExpByCategory(inventory, value as IEquipmentClient[], key as TEquipmentKey); applyClientEquipmentUpdates(inventory, value as IEquipmentClient[], key as TEquipmentKey);
} }
break; break;
// if ( // if (
@ -802,8 +858,7 @@ export const addMissionRewards = async (
for (const reward of fixedLevelRewards.levelKeyRewards2) { for (const reward of fixedLevelRewards.levelKeyRewards2) {
//quest stage completion credit rewards //quest stage completion credit rewards
if (reward.rewardType == "RT_CREDITS") { if (reward.rewardType == "RT_CREDITS") {
inventory.RegularCredits += reward.amount; missionCompletionCredits += reward.amount; // will be added to inventory in addCredits
missionCompletionCredits += reward.amount;
continue; continue;
} }
MissionRewards.push({ MissionRewards.push({
@ -864,7 +919,7 @@ export const addMissionRewards = async (
if (rewardInfo.useVaultManifest) { if (rewardInfo.useVaultManifest) {
MissionRewards.push({ MissionRewards.push({
StoreItem: getRandomElement(corruptedMods), StoreItem: getRandomElement(corruptedMods)!,
ItemCount: 1 ItemCount: 1
}); });
} }

View File

@ -47,12 +47,12 @@ export const createGarden = (): IGardeningDatabase => {
Name: "Garden0", Name: "Garden0",
Plants: [ Plants: [
{ {
PlantType: getRandomElement(plantTypes), PlantType: getRandomElement(plantTypes)!,
EndTime: endTime, EndTime: endTime,
PlotIndex: 0 PlotIndex: 0
}, },
{ {
PlantType: getRandomElement(plantTypes), PlantType: getRandomElement(plantTypes)!,
EndTime: endTime, EndTime: endTime,
PlotIndex: 1 PlotIndex: 1
} }
@ -62,12 +62,12 @@ export const createGarden = (): IGardeningDatabase => {
Name: "Garden1", Name: "Garden1",
Plants: [ Plants: [
{ {
PlantType: getRandomElement(plantTypes), PlantType: getRandomElement(plantTypes)!,
EndTime: endTime, EndTime: endTime,
PlotIndex: 0 PlotIndex: 0
}, },
{ {
PlantType: getRandomElement(plantTypes), PlantType: getRandomElement(plantTypes)!,
EndTime: endTime, EndTime: endTime,
PlotIndex: 1 PlotIndex: 1
} }
@ -77,12 +77,12 @@ export const createGarden = (): IGardeningDatabase => {
Name: "Garden2", Name: "Garden2",
Plants: [ Plants: [
{ {
PlantType: getRandomElement(plantTypes), PlantType: getRandomElement(plantTypes)!,
EndTime: endTime, EndTime: endTime,
PlotIndex: 0 PlotIndex: 0
}, },
{ {
PlantType: getRandomElement(plantTypes), PlantType: getRandomElement(plantTypes)!,
EndTime: endTime, EndTime: endTime,
PlotIndex: 1 PlotIndex: 1
} }

View File

@ -223,10 +223,10 @@ export const handlePurchase = async (
purchaseResponse.Standing = [ purchaseResponse.Standing = [
{ {
Tag: syndicateTag, Tag: syndicateTag,
Standing: favour.standingCost Standing: favour.standingCost * purchaseRequest.PurchaseParams.Quantity
} }
]; ];
affiliation.Standing -= favour.standingCost; affiliation.Standing -= favour.standingCost * purchaseRequest.PurchaseParams.Quantity;
} }
} }
} }

View File

@ -6,7 +6,7 @@ export interface IRngResult {
probability: number; probability: number;
} }
export const getRandomElement = <T>(arr: T[]): T => { export const getRandomElement = <T>(arr: readonly T[]): T | undefined => {
return arr[Math.floor(Math.random() * arr.length)]; return arr[Math.floor(Math.random() * arr.length)];
}; };
@ -18,7 +18,10 @@ export const getRandomInt = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
}; };
const getRewardAtPercentage = <T extends { probability: number }>(pool: T[], percentage: number): T | undefined => { export const getRewardAtPercentage = <T extends { probability: number }>(
pool: T[],
percentage: number
): T | undefined => {
if (pool.length == 0) return; if (pool.length == 0) return;
const totalChance = pool.reduce((accum, item) => accum + item.probability, 0); const totalChance = pool.reduce((accum, item) => accum + item.probability, 0);
@ -110,7 +113,7 @@ export class CRng {
return min; return min;
} }
randomElement<T>(arr: T[]): T { randomElement<T>(arr: readonly T[]): T | undefined {
return arr[Math.floor(this.random() * arr.length)]; return arr[Math.floor(this.random() * arr.length)];
} }
@ -142,7 +145,7 @@ export class SRng {
return min; return min;
} }
randomElement<T>(arr: T[]): T { randomElement<T>(arr: readonly T[]): T | undefined {
return arr[this.randomInt(0, arr.length - 1)]; return arr[this.randomInt(0, arr.length - 1)];
} }

View File

@ -1,4 +1,5 @@
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "@/src/constants/timeConstants";
import { catBreadHash } from "@/src/helpers/stringHelpers";
import { CRng, mixSeeds } from "@/src/services/rngService"; import { CRng, mixSeeds } from "@/src/services/rngService";
import { IMongoDate } from "@/src/types/commonTypes"; import { IMongoDate } from "@/src/types/commonTypes";
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
@ -6,7 +7,6 @@ import { ExportVendors, IRange } from "warframe-public-export-plus";
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json"; import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json"; import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
import DeimosFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosFishmongerVendorManifest.json";
import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json"; import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json";
import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json"; import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json";
import DeimosHivemindCommisionsManifestProspector from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json"; import DeimosHivemindCommisionsManifestProspector from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json";
@ -22,12 +22,10 @@ import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorI
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json"; import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json"; import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
import OstronFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json";
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json"; import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json";
import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json"; import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json";
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json";
import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json"; import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json";
import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json"; import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json";
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json"; import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
@ -36,7 +34,6 @@ import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVe
const rawVendorManifests: IVendorManifest[] = [ const rawVendorManifests: IVendorManifest[] = [
ArchimedeanVendorManifest, ArchimedeanVendorManifest,
DeimosEntratiFragmentVendorProductsManifest, DeimosEntratiFragmentVendorProductsManifest,
DeimosFishmongerVendorManifest,
DeimosHivemindCommisionsManifestFishmonger, DeimosHivemindCommisionsManifestFishmonger,
DeimosHivemindCommisionsManifestPetVendor, DeimosHivemindCommisionsManifestPetVendor,
DeimosHivemindCommisionsManifestProspector, DeimosHivemindCommisionsManifestProspector,
@ -52,12 +49,10 @@ const rawVendorManifests: IVendorManifest[] = [
HubsRailjackCrewMemberVendorManifest, HubsRailjackCrewMemberVendorManifest,
MaskSalesmanManifest, MaskSalesmanManifest,
Nova1999ConquestShopManifest, Nova1999ConquestShopManifest,
OstronFishmongerVendorManifest,
OstronPetVendorManifest, OstronPetVendorManifest,
OstronProspectorVendorManifest, OstronProspectorVendorManifest,
RadioLegionIntermission12VendorManifest, RadioLegionIntermission12VendorManifest,
SolarisDebtTokenVendorRepossessionsManifest, SolarisDebtTokenVendorRepossessionsManifest,
SolarisFishmongerVendorManifest,
SolarisProspectorVendorManifest, SolarisProspectorVendorManifest,
Temple1999VendorManifest, Temple1999VendorManifest,
TeshinHardModeVendorManifest, // uses preprocessing TeshinHardModeVendorManifest, // uses preprocessing
@ -87,17 +82,11 @@ const generatableVendors: IGeneratableVendorInfo[] = [
cycleOffset: 1744934400_000, cycleOffset: 1744934400_000,
cycleDuration: 4 * unixTimesInMs.day cycleDuration: 4 * unixTimesInMs.day
}, },
{
_id: { $oid: "5be4a159b144f3cdf1c22efa" },
TypeName: "/Lotus/Types/Game/VendorManifests/Solaris/DebtTokenVendorManifest",
RandomSeedType: "VRST_FLAVOUR_TEXT",
cycleDuration: unixTimesInMs.hour
},
{ {
_id: { $oid: "61ba123467e5d37975aeeb03" }, _id: { $oid: "61ba123467e5d37975aeeb03" },
TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest", TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
RandomSeedType: "VRST_FLAVOUR_TEXT", RandomSeedType: "VRST_FLAVOUR_TEXT",
cycleDuration: unixTimesInMs.week cycleDuration: unixTimesInMs.week // TODO: Auto-detect this based on the items, so we don't need to specify it explicitly.
} }
// { // {
// _id: { $oid: "5dbb4c41e966f7886c3ce939" }, // _id: { $oid: "5dbb4c41e966f7886c3ce939" },
@ -105,6 +94,10 @@ const generatableVendors: IGeneratableVendorInfo[] = [
// } // }
]; ];
const getVendorOid = (typeName: string): string => {
return "5be4a159b144f3cd" + catBreadHash(typeName).toString(16).padStart(8, "0");
};
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => { export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) { for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo.TypeName == typeName) { if (vendorManifest.VendorInfo.TypeName == typeName) {
@ -116,6 +109,14 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest |
return generateVendorManifest(vendorInfo); return generateVendorManifest(vendorInfo);
} }
} }
if (typeName in ExportVendors) {
return generateVendorManifest({
_id: { $oid: getVendorOid(typeName) },
TypeName: typeName,
RandomSeedType: ExportVendors[typeName].randomSeedType,
cycleDuration: unixTimesInMs.hour
});
}
return undefined; return undefined;
}; };
@ -130,6 +131,17 @@ export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined
return generateVendorManifest(vendorInfo); return generateVendorManifest(vendorInfo);
} }
} }
for (const [typeName, manifest] of Object.entries(ExportVendors)) {
const typeNameOid = getVendorOid(typeName);
if (typeNameOid == oid) {
return generateVendorManifest({
_id: { $oid: typeNameOid },
TypeName: typeName,
RandomSeedType: manifest.randomSeedType,
cycleDuration: unixTimesInMs.hour
});
}
}
return undefined; return undefined;
}; };
@ -195,12 +207,12 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
const rng = new CRng(mixSeeds(vendorSeed, cycleIndex)); const rng = new CRng(mixSeeds(vendorSeed, cycleIndex));
const manifest = ExportVendors[vendorInfo.TypeName]; const manifest = ExportVendors[vendorInfo.TypeName];
const offersToAdd = []; const offersToAdd = [];
if (manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue) { if (manifest.numItems && !manifest.isOneBinPerCycle) {
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue); const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
while (processed.ItemManifest.length + offersToAdd.length < numItemsTarget) { while (processed.ItemManifest.length + offersToAdd.length < numItemsTarget) {
// TODO: Consider per-bin item limits // TODO: Consider per-bin item limits
// TODO: Consider item probability weightings // TODO: Consider item probability weightings
offersToAdd.push(rng.randomElement(manifest.items)); offersToAdd.push(rng.randomElement(manifest.items)!);
} }
} else { } else {
let binThisCycle; let binThisCycle;
@ -244,7 +256,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
for (let i = 0; i != rawItem.numRandomItemPrices; ++i) { for (let i = 0; i != rawItem.numRandomItemPrices; ++i) {
let itemPrice: { type: string; count: IRange }; let itemPrice: { type: string; count: IRange };
do { do {
itemPrice = rng.randomElement(manifest.randomItemPricesPerBin![rawItem.bin]); itemPrice = rng.randomElement(manifest.randomItemPricesPerBin![rawItem.bin])!;
} while (item.ItemPrices.find(x => x.ItemType == itemPrice.type)); } while (item.ItemPrices.find(x => x.ItemType == itemPrice.type));
item.ItemPrices.push({ item.ItemPrices.push({
ItemType: itemPrice.type, ItemType: itemPrice.type,
@ -263,11 +275,18 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
) * rawItem.credits.step; ) * rawItem.credits.step;
item.RegularPrice = [value, value]; item.RegularPrice = [value, value];
} }
if (rawItem.platinum) {
const value =
typeof rawItem.platinum == "number"
? rawItem.platinum
: rng.randomInt(rawItem.platinum.minValue, rawItem.platinum.maxValue);
item.PremiumPrice = [value, value];
}
if (vendorInfo.RandomSeedType) { if (vendorInfo.RandomSeedType) {
item.LocTagRandSeed = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff); item.LocTagRandSeed = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
if (vendorInfo.RandomSeedType == "VRST_WEAPON") { if (vendorInfo.RandomSeedType == "VRST_WEAPON") {
const highDword = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff); const highDword = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
item.LocTagRandSeed = (BigInt(highDword) << 32n) | BigInt(item.LocTagRandSeed); item.LocTagRandSeed = (BigInt(highDword) << 32n) | (BigInt(item.LocTagRandSeed) & 0xffffffffn);
} }
} }
processed.ItemManifest.push(item); processed.ItemManifest.push(item);

View File

@ -33,9 +33,11 @@ const sortieBosses = [
"SORTIE_BOSS_LEPHANTIS", "SORTIE_BOSS_LEPHANTIS",
"SORTIE_BOSS_INFALAD", "SORTIE_BOSS_INFALAD",
"SORTIE_BOSS_CORRUPTED_VOR" "SORTIE_BOSS_CORRUPTED_VOR"
]; ] as const;
const sortieBossToFaction: Record<string, string> = { type TSortieBoss = (typeof sortieBosses)[number];
const sortieBossToFaction: Record<TSortieBoss, string> = {
SORTIE_BOSS_HYENA: "FC_CORPUS", SORTIE_BOSS_HYENA: "FC_CORPUS",
SORTIE_BOSS_KELA: "FC_GRINEER", SORTIE_BOSS_KELA: "FC_GRINEER",
SORTIE_BOSS_VOR: "FC_GRINEER", SORTIE_BOSS_VOR: "FC_GRINEER",
@ -74,21 +76,22 @@ const sortieFactionToSpecialMissionTileset: Record<string, string> = {
FC_INFESTATION: "CorpusShipTileset" FC_INFESTATION: "CorpusShipTileset"
}; };
const sortieBossNode: Record<string, string> = { const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">, string> = {
SORTIE_BOSS_HYENA: "SolNode127",
SORTIE_BOSS_KELA: "SolNode193",
SORTIE_BOSS_VOR: "SolNode108",
SORTIE_BOSS_RUK: "SolNode32",
SORTIE_BOSS_HEK: "SolNode24",
SORTIE_BOSS_KRIL: "SolNode99",
SORTIE_BOSS_TYL: "SolNode105",
SORTIE_BOSS_JACKAL: "SolNode104",
SORTIE_BOSS_ALAD: "SolNode53", SORTIE_BOSS_ALAD: "SolNode53",
SORTIE_BOSS_AMBULAS: "SolNode51", SORTIE_BOSS_AMBULAS: "SolNode51",
SORTIE_BOSS_NEF: "SettlementNode20", SORTIE_BOSS_HEK: "SolNode24",
SORTIE_BOSS_RAPTOR: "SolNode210", SORTIE_BOSS_HYENA: "SolNode127",
SORTIE_BOSS_INFALAD: "SolNode166",
SORTIE_BOSS_JACKAL: "SolNode104",
SORTIE_BOSS_KELA: "SolNode193",
SORTIE_BOSS_KRIL: "SolNode99",
SORTIE_BOSS_LEPHANTIS: "SolNode712", SORTIE_BOSS_LEPHANTIS: "SolNode712",
SORTIE_BOSS_INFALAD: "SolNode705" SORTIE_BOSS_NEF: "SettlementNode20",
SORTIE_BOSS_PHORID: "SolNode171",
SORTIE_BOSS_RAPTOR: "SolNode210",
SORTIE_BOSS_RUK: "SolNode32",
SORTIE_BOSS_TYL: "SolNode105",
SORTIE_BOSS_VOR: "SolNode108"
}; };
const eidolonJobs = [ const eidolonJobs = [
@ -225,41 +228,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
const seed = new CRng(day).randomInt(0, 0xffff); const seed = new CRng(day).randomInt(0, 0xffff);
const rng = new CRng(seed); const rng = new CRng(seed);
const boss = rng.randomElement(sortieBosses); const boss = rng.randomElement(sortieBosses)!;
const modifiers = [
"SORTIE_MODIFIER_LOW_ENERGY",
"SORTIE_MODIFIER_IMPACT",
"SORTIE_MODIFIER_SLASH",
"SORTIE_MODIFIER_PUNCTURE",
"SORTIE_MODIFIER_EXIMUS",
"SORTIE_MODIFIER_MAGNETIC",
"SORTIE_MODIFIER_CORROSIVE",
"SORTIE_MODIFIER_VIRAL",
"SORTIE_MODIFIER_ELECTRICITY",
"SORTIE_MODIFIER_RADIATION",
"SORTIE_MODIFIER_GAS",
"SORTIE_MODIFIER_FIRE",
"SORTIE_MODIFIER_EXPLOSION",
"SORTIE_MODIFIER_FREEZE",
"SORTIE_MODIFIER_TOXIN",
"SORTIE_MODIFIER_POISON",
"SORTIE_MODIFIER_HAZARD_RADIATION",
"SORTIE_MODIFIER_HAZARD_MAGNETIC",
"SORTIE_MODIFIER_HAZARD_FOG", // TODO: push this if the mission tileset is Grineer Forest
"SORTIE_MODIFIER_HAZARD_FIRE", // TODO: push this if the mission tileset is Corpus Ship or Grineer Galleon
"SORTIE_MODIFIER_HAZARD_ICE",
"SORTIE_MODIFIER_HAZARD_COLD",
"SORTIE_MODIFIER_SECONDARY_ONLY",
"SORTIE_MODIFIER_SHOTGUN_ONLY",
"SORTIE_MODIFIER_SNIPER_ONLY",
"SORTIE_MODIFIER_RIFLE_ONLY",
"SORTIE_MODIFIER_MELEE_ONLY",
"SORTIE_MODIFIER_BOW_ONLY"
];
if (sortieBossToFaction[boss] == "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_SHIELDS");
if (sortieBossToFaction[boss] != "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_ARMOR");
const nodes: string[] = []; const nodes: string[] = [];
const availableMissionIndexes: number[] = []; const availableMissionIndexes: number[] = [];
@ -269,7 +238,11 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) && sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
key in sortieTilesets key in sortieTilesets
) { ) {
if (!availableMissionIndexes.includes(value.missionIndex)) { if (
value.missionIndex != 0 && // Assassination will be decided independently
value.missionIndex != 5 && // Sorties do not have capture missions
!availableMissionIndexes.includes(value.missionIndex)
) {
availableMissionIndexes.push(value.missionIndex); availableMissionIndexes.push(value.missionIndex);
} }
nodes.push(key); nodes.push(key);
@ -299,7 +272,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
missionIndex = ExportRegions[node].missionIndex; missionIndex = ExportRegions[node].missionIndex;
if (missionIndex != 28) { if (missionIndex != 28) {
missionIndex = rng.randomElement(availableMissionIndexes); missionIndex = rng.randomElement(availableMissionIndexes)!;
} }
} while ( } while (
specialMissionTypes.indexOf(missionIndex) != -1 && specialMissionTypes.indexOf(missionIndex) != -1 &&
@ -307,28 +280,61 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
sortieFactionToSpecialMissionTileset[sortieBossToFaction[boss]] sortieFactionToSpecialMissionTileset[sortieBossToFaction[boss]]
); );
if (i == 2 && rng.randomInt(0, 2) == 2) { const modifiers = [
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY"); "SORTIE_MODIFIER_LOW_ENERGY",
const modifierType = rng.randomElement(filteredModifiers); "SORTIE_MODIFIER_IMPACT",
"SORTIE_MODIFIER_SLASH",
if (boss == "SORTIE_BOSS_PHORID") { "SORTIE_MODIFIER_PUNCTURE",
selectedNodes.push({ "SORTIE_MODIFIER_EXIMUS",
missionType: "MT_ASSASSINATION", "SORTIE_MODIFIER_MAGNETIC",
modifierType, "SORTIE_MODIFIER_CORROSIVE",
node, "SORTIE_MODIFIER_VIRAL",
tileset: sortieTilesets[node as keyof typeof sortieTilesets] "SORTIE_MODIFIER_ELECTRICITY",
}); "SORTIE_MODIFIER_RADIATION",
nodes.splice(randomIndex, 1); "SORTIE_MODIFIER_GAS",
continue; "SORTIE_MODIFIER_FIRE",
} else if (sortieBossNode[boss]) { "SORTIE_MODIFIER_EXPLOSION",
selectedNodes.push({ "SORTIE_MODIFIER_FREEZE",
missionType: "MT_ASSASSINATION", "SORTIE_MODIFIER_TOXIN",
modifierType, "SORTIE_MODIFIER_POISON",
node: sortieBossNode[boss], "SORTIE_MODIFIER_SECONDARY_ONLY",
tileset: sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets] "SORTIE_MODIFIER_SHOTGUN_ONLY",
}); "SORTIE_MODIFIER_SNIPER_ONLY",
continue; "SORTIE_MODIFIER_RIFLE_ONLY",
"SORTIE_MODIFIER_BOW_ONLY"
];
const pushTilesetModifiers = (tileset: string): void => {
switch (tileset) {
case "GrineerForestTileset":
modifiers.push("SORTIE_MODIFIER_HAZARD_FOG");
break;
case "CorpusShipTileset":
case "GrineerGalleonTileset":
case "InfestedCorpusShipTileset":
modifiers.push("SORTIE_MODIFIER_HAZARD_MAGNETIC");
modifiers.push("SORTIE_MODIFIER_HAZARD_FIRE");
modifiers.push("SORTIE_MODIFIER_HAZARD_ICE");
break;
case "CorpusIcePlanetTileset":
case "CorpusIcePlanetTilesetCaves":
modifiers.push("SORTIE_MODIFIER_HAZARD_COLD");
break;
} }
};
if (i == 2 && boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2) {
const tileset = sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets];
pushTilesetModifiers(tileset);
const modifierType = rng.randomElement(modifiers)!;
selectedNodes.push({
missionType: "MT_ASSASSINATION",
modifierType,
node: sortieBossNode[boss],
tileset
});
continue;
} }
const missionType = eMissionType[missionIndex].tag; const missionType = eMissionType[missionIndex].tag;
@ -338,18 +344,30 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
continue; continue;
} }
const filteredModifiers = modifiers.push("SORTIE_MODIFIER_MELEE_ONLY"); // not an assassination mission, can now push this
missionType === "MT_TERRITORY"
? modifiers.filter(mod => mod != "SORTIE_MODIFIER_HAZARD_RADIATION")
: modifiers;
const modifierType = rng.randomElement(filteredModifiers); if (missionType != "MT_TERRITORY") {
modifiers.push("SORTIE_MODIFIER_HAZARD_RADIATION");
}
if (ExportRegions[node].factionIndex == 0) {
// Grineer
modifiers.push("SORTIE_MODIFIER_ARMOR");
} else if (ExportRegions[node].factionIndex == 1) {
// Corpus
modifiers.push("SORTIE_MODIFIER_SHIELDS");
}
const tileset = sortieTilesets[node as keyof typeof sortieTilesets];
pushTilesetModifiers(tileset);
const modifierType = rng.randomElement(modifiers)!;
selectedNodes.push({ selectedNodes.push({
missionType, missionType,
modifierType, modifierType,
node, node,
tileset: sortieTilesets[node as keyof typeof sortieTilesets] tileset
}); });
nodes.splice(randomIndex, 1); nodes.splice(randomIndex, 1);
missionTypes.add(missionType); missionTypes.add(missionType);
@ -386,7 +404,7 @@ const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
Daily: true, Daily: true,
Activation: { $date: { $numberLong: dayStart.toString() } }, Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } }, Expiry: { $date: { $numberLong: dayEnd.toString() } },
Challenge: rng.randomElement(dailyChallenges) Challenge: rng.randomElement(dailyChallenges)!
}; };
}; };
@ -405,7 +423,7 @@ const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge =>
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") }, _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: rng.randomElement(weeklyChallenges) Challenge: rng.randomElement(weeklyChallenges)!
}; };
}; };
@ -422,7 +440,7 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") }, _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: rng.randomElement(weeklyHardChallenges) Challenge: rng.randomElement(weeklyHardChallenges)!
}; };
}; };
@ -721,6 +739,11 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
SyndicateMissions: [...staticWorldState.SyndicateMissions] 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 (config.worldState?.starDays) { if (config.worldState?.starDays) {
worldState.Goals.push({ worldState.Goals.push({
_id: { $oid: "67a4dcce2a198564d62e1647" }, _id: { $oid: "67a4dcce2a198564d62e1647" },
@ -1193,7 +1216,7 @@ export const getLiteSortie = (week: number): ILiteSortie => {
"MT_EXTERMINATION", "MT_EXTERMINATION",
"MT_SABOTAGE", "MT_SABOTAGE",
"MT_RESCUE" "MT_RESCUE"
]), ])!,
node: firstNode node: firstNode
}, },
{ {
@ -1203,8 +1226,8 @@ export const getLiteSortie = (week: number): ILiteSortie => {
"MT_ARTIFACT", "MT_ARTIFACT",
"MT_EXCAVATE", "MT_EXCAVATE",
"MT_SURVIVAL" "MT_SURVIVAL"
]), ])!,
node: rng.randomElement(nodes) node: rng.randomElement(nodes)!
}, },
{ {
missionType: "MT_ASSASSINATION", missionType: "MT_ASSASSINATION",
@ -1224,3 +1247,20 @@ export const isArchwingMission = (node: IRegion): boolean => {
} }
return false; return false;
}; };
export const version_compare = (a: string, b: string): number => {
const a_digits = a
.split("/")[0]
.split(".")
.map(x => parseInt(x));
const b_digits = b
.split("/")[0]
.split(".")
.map(x => parseInt(x));
for (let i = 0; i != a_digits.length; ++i) {
if (a_digits[i] != b_digits[i]) {
return a_digits[i] > b_digits[i] ? 1 : -1;
}
}
return 0;
};

View File

@ -43,6 +43,7 @@ export interface IInventoryDatabase
| "RecentVendorPurchases" | "RecentVendorPurchases"
| "NextRefill" | "NextRefill"
| "Nemesis" | "Nemesis"
| "NemesisHistory"
| "EntratiVaultCountResetDate" | "EntratiVaultCountResetDate"
| "BrandedSuits" | "BrandedSuits"
| "LockedWeaponGroup" | "LockedWeaponGroup"
@ -51,6 +52,7 @@ export interface IInventoryDatabase
| "LastLiteSortieReward" | "LastLiteSortieReward"
| "CrewMembers" | "CrewMembers"
| "QualifyingInvasions" | "QualifyingInvasions"
| "LastInventorySync"
| TEquipmentKey | TEquipmentKey
>, >,
InventoryDatabaseEquipment { InventoryDatabaseEquipment {
@ -79,6 +81,7 @@ export interface IInventoryDatabase
RecentVendorPurchases?: IRecentVendorPurchaseDatabase[]; RecentVendorPurchases?: IRecentVendorPurchaseDatabase[];
NextRefill?: Date; NextRefill?: Date;
Nemesis?: INemesisDatabase; Nemesis?: INemesisDatabase;
NemesisHistory?: INemesisBaseDatabase[];
EntratiVaultCountResetDate?: Date; EntratiVaultCountResetDate?: Date;
BrandedSuits?: Types.ObjectId[]; BrandedSuits?: Types.ObjectId[];
LockedWeaponGroup?: ILockedWeaponGroupDatabase; LockedWeaponGroup?: ILockedWeaponGroupDatabase;
@ -87,6 +90,7 @@ export interface IInventoryDatabase
LastLiteSortieReward?: ILastSortieRewardDatabase[]; LastLiteSortieReward?: ILastSortieRewardDatabase[];
CrewMembers: ICrewMemberDatabase[]; CrewMembers: ICrewMemberDatabase[];
QualifyingInvasions: IInvasionProgressDatabase[]; QualifyingInvasions: IInvasionProgressDatabase[];
LastInventorySync?: Types.ObjectId;
} }
export interface IQuestKeyDatabase { export interface IQuestKeyDatabase {
@ -198,7 +202,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
OperatorLoadOuts: IOperatorConfigClient[]; OperatorLoadOuts: IOperatorConfigClient[];
KahlLoadOuts: IOperatorConfigClient[]; KahlLoadOuts: IOperatorConfigClient[];
DuviriInfo: IDuviriInfo; DuviriInfo?: IDuviriInfo;
Mailbox?: IMailboxClient; Mailbox?: IMailboxClient;
SubscribedToEmails: number; SubscribedToEmails: number;
Created: IMongoDate; Created: IMongoDate;
@ -256,7 +260,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
EquippedGear: string[]; EquippedGear: string[];
DeathMarks: string[]; DeathMarks: string[];
FusionTreasures: IFusionTreasure[]; FusionTreasures: IFusionTreasure[];
WebFlags: IWebFlags; //WebFlags: IWebFlags;
CompletedAlerts: string[]; CompletedAlerts: string[];
Consumables: ITypeCount[]; Consumables: ITypeCount[];
LevelKeys: ITypeCount[]; LevelKeys: ITypeCount[];
@ -266,10 +270,10 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
KubrowPetEggs?: IKubrowPetEggClient[]; KubrowPetEggs?: IKubrowPetEggClient[];
LoreFragmentScans: ILoreFragmentScan[]; LoreFragmentScans: ILoreFragmentScan[];
EquippedEmotes: string[]; EquippedEmotes: string[];
PendingTrades: IPendingTrade[]; //PendingTrades: IPendingTrade[];
Boosters: IBooster[]; Boosters: IBooster[];
ActiveDojoColorResearch: string; ActiveDojoColorResearch: string;
SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters; //SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters;
SupportedSyndicate?: string; SupportedSyndicate?: string;
Affiliations: IAffiliation[]; Affiliations: IAffiliation[];
QualifyingInvasions: IInvasionProgressClient[]; QualifyingInvasions: IInvasionProgressClient[];
@ -291,19 +295,19 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
ActiveAvatarImageType: string; ActiveAvatarImageType: string;
ShipDecorations: ITypeCount[]; ShipDecorations: ITypeCount[];
DiscoveredMarkers: IDiscoveredMarker[]; DiscoveredMarkers: IDiscoveredMarker[];
CompletedJobs: ICompletedJob[]; //CompletedJobs: ICompletedJob[];
FocusAbility?: string; FocusAbility?: string;
FocusUpgrades: IFocusUpgrade[]; FocusUpgrades: IFocusUpgrade[];
HasContributedToDojo?: boolean; HasContributedToDojo?: boolean;
HWIDProtectEnabled?: boolean; HWIDProtectEnabled?: boolean;
KubrowPetPrints: IKubrowPetPrint[]; //KubrowPetPrints: IKubrowPetPrint[];
AlignmentReplay?: IAlignment; AlignmentReplay?: IAlignment;
PersonalGoalProgress: IPersonalGoalProgress[]; //PersonalGoalProgress: IPersonalGoalProgress[];
ThemeStyle: string; ThemeStyle: string;
ThemeBackground: string; ThemeBackground: string;
ThemeSounds: string; ThemeSounds: string;
BountyScore: number; BountyScore: number;
ChallengeInstanceStates: IChallengeInstanceState[]; //ChallengeInstanceStates: IChallengeInstanceState[];
LoginMilestoneRewards: string[]; LoginMilestoneRewards: string[];
RecentVendorPurchases?: IRecentVendorPurchaseClient[]; RecentVendorPurchases?: IRecentVendorPurchaseClient[];
NodeIntrosCompleted: string[]; NodeIntrosCompleted: string[];
@ -311,17 +315,17 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
CompletedJobChains?: ICompletedJobChain[]; CompletedJobChains?: ICompletedJobChain[];
SeasonChallengeHistory: ISeasonChallenge[]; SeasonChallengeHistory: ISeasonChallenge[];
EquippedInstrument?: string; EquippedInstrument?: string;
InvasionChainProgress: IInvasionChainProgress[]; //InvasionChainProgress: IInvasionChainProgress[];
Nemesis?: INemesisClient; Nemesis?: INemesisClient;
NemesisHistory: INemesisBaseClient[]; NemesisHistory?: INemesisBaseClient[];
LastNemesisAllySpawnTime?: IMongoDate; //LastNemesisAllySpawnTime?: IMongoDate;
Settings?: ISettings; Settings?: ISettings;
PersonalTechProjects: IPersonalTechProjectClient[]; PersonalTechProjects: IPersonalTechProjectClient[];
PlayerSkills: IPlayerSkills; PlayerSkills: IPlayerSkills;
CrewShipAmmo: ITypeCount[]; CrewShipAmmo: ITypeCount[];
CrewShipWeaponSkins: IUpgradeClient[]; CrewShipWeaponSkins: IUpgradeClient[];
CrewShipSalvagedWeaponSkins: IUpgradeClient[]; CrewShipSalvagedWeaponSkins: IUpgradeClient[];
TradeBannedUntil?: IMongoDate; //TradeBannedUntil?: IMongoDate;
PlayedParkourTutorial: boolean; PlayedParkourTutorial: boolean;
SubscribedToEmailsPersonalized: number; SubscribedToEmailsPersonalized: number;
InfestedFoundry?: IInfestedFoundryClient; InfestedFoundry?: IInfestedFoundryClient;
@ -331,17 +335,17 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
LotusCustomization?: ILotusCustomization; LotusCustomization?: ILotusCustomization;
UseAdultOperatorLoadout?: boolean; UseAdultOperatorLoadout?: boolean;
NemesisAbandonedRewards: string[]; NemesisAbandonedRewards: string[];
LastInventorySync: IOid; LastInventorySync?: IOid;
NextRefill?: IMongoDate; NextRefill?: IMongoDate;
FoundToday?: IMiscItem[]; // for Argon Crystals FoundToday?: IMiscItem[]; // for Argon Crystals
CustomMarkers?: ICustomMarkers[]; CustomMarkers?: ICustomMarkers[];
ActiveLandscapeTraps: any[]; //ActiveLandscapeTraps: any[];
EvolutionProgress?: IEvolutionProgress[]; EvolutionProgress?: IEvolutionProgress[];
RepVotes: any[]; //RepVotes: any[];
LeagueTickets: any[]; //LeagueTickets: any[];
Quests: any[]; //Quests: any[];
Robotics: any[]; //Robotics: any[];
UsedDailyDeals: any[]; //UsedDailyDeals: any[];
LibraryPersonalTarget?: string; LibraryPersonalTarget?: string;
LibraryPersonalProgress: ILibraryPersonalProgress[]; LibraryPersonalProgress: ILibraryPersonalProgress[];
CollectibleSeries?: ICollectibleEntry[]; CollectibleSeries?: ICollectibleEntry[];
@ -902,8 +906,8 @@ export interface IPendingRecipeDatabase {
ItemType: string; ItemType: string;
CompletionDate: Date; CompletionDate: Date;
ItemId: IOid; ItemId: IOid;
TargetItemId?: string; // likely related to liches TargetItemId?: string; // unsure what this is for
TargetFingerprint?: string; // likely related to liches TargetFingerprint?: string;
LongGuns?: IEquipmentDatabase[]; LongGuns?: IEquipmentDatabase[];
Pistols?: IEquipmentDatabase[]; Pistols?: IEquipmentDatabase[];
Melee?: IEquipmentDatabase[]; Melee?: IEquipmentDatabase[];
@ -951,6 +955,17 @@ export interface ICrewShipComponentFingerprint extends IInnateDamageFingerprint
SubroutineIndex?: number; SubroutineIndex?: number;
} }
export interface INemesisWeaponTargetFingerprint {
ItemType: string;
UpgradeFingerprint: IInnateDamageFingerprint;
Name: string;
}
export interface INemesisPetTargetFingerprint {
Parts: string[];
Name: string;
}
export enum GettingSlotOrderInfo { export enum GettingSlotOrderInfo {
Empty = "", Empty = "",
LotusUpgradesModsRandomizedPlayerMeleeWeaponRandomModRare0 = "/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare:0", LotusUpgradesModsRandomizedPlayerMeleeWeaponRandomModRare0 = "/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare:0",

View File

@ -3,9 +3,9 @@ import { Types } from "mongoose";
export interface IAccountAndLoginResponseCommons { export interface IAccountAndLoginResponseCommons {
DisplayName: string; DisplayName: string;
CountryCode: string; CountryCode: string;
ClientType: string; ClientType?: string;
CrossPlatformAllowed: boolean; CrossPlatformAllowed?: boolean;
ForceLogoutVersion: number; ForceLogoutVersion?: number;
AmazonAuthToken?: string; AmazonAuthToken?: string;
AmazonRefreshToken?: string; AmazonRefreshToken?: string;
ConsentNeeded: boolean; ConsentNeeded: boolean;
@ -16,6 +16,7 @@ export interface IAccountAndLoginResponseCommons {
export interface IDatabaseAccountRequiredFields extends IAccountAndLoginResponseCommons { export interface IDatabaseAccountRequiredFields extends IAccountAndLoginResponseCommons {
email: string; email: string;
password: string; password: string;
BuildLabel?: string;
} }
export interface IDatabaseAccount extends IDatabaseAccountRequiredFields { export interface IDatabaseAccount extends IDatabaseAccountRequiredFields {
@ -44,12 +45,12 @@ export interface ILoginRequest {
export interface ILoginResponse extends IAccountAndLoginResponseCommons { export interface ILoginResponse extends IAccountAndLoginResponseCommons {
id: string; id: string;
Groups: IGroup[]; Groups?: IGroup[];
BuildLabel: string; BuildLabel: string;
MatchmakingBuildId: string; MatchmakingBuildId?: string;
platformCDNs?: string[]; platformCDNs?: string[];
NRS?: string[]; NRS?: string[];
DTLS: number; DTLS?: number;
IRC: string[]; IRC: string[];
HUB?: string; HUB?: string;
} }

View File

@ -21,7 +21,9 @@ import {
ILockedWeaponGroupClient, ILockedWeaponGroupClient,
ILoadOutPresets, ILoadOutPresets,
IInvasionProgressClient, IInvasionProgressClient,
IWeaponSkinClient IWeaponSkinClient,
IKubrowPetEggClient,
INemesisClient
} from "./inventoryTypes/inventoryTypes"; } from "./inventoryTypes/inventoryTypes";
import { IGroup } from "./loginTypes"; import { IGroup } from "./loginTypes";
@ -73,6 +75,14 @@ export type IMissionInventoryUpdateRequest = {
PS: string; PS: string;
ActiveDojoColorResearch: string; ActiveDojoColorResearch: string;
RewardInfo?: IRewardInfo; RewardInfo?: IRewardInfo;
NemesisKillConvert?: {
nemesisName: string;
weaponLoc: string;
petLoc: "" | "/Lotus/Language/Pets/ZanukaPetName";
fingerprint: bigint | number;
killed: boolean;
};
target?: INemesisClient;
ReceivedCeremonyMsg: boolean; ReceivedCeremonyMsg: boolean;
LastCeremonyResetDate: number; LastCeremonyResetDate: number;
MissionPTS: number; MissionPTS: number;
@ -118,7 +128,9 @@ export type IMissionInventoryUpdateRequest = {
NumExtraRewards: number; NumExtraRewards: number;
Count: number; Count: number;
}[]; }[];
KubrowPetEggs?: IKubrowPetEggClient[];
DiscoveredMarkers?: IDiscoveredMarker[]; DiscoveredMarkers?: IDiscoveredMarker[];
BrandedSuits?: IOid; // sent when captured by g3
LockedWeaponGroup?: ILockedWeaponGroupClient; // sent when captured by zanuka LockedWeaponGroup?: ILockedWeaponGroupClient; // sent when captured by zanuka
UnlockWeapons?: boolean; // sent when recovered weapons from zanuka capture UnlockWeapons?: boolean; // sent when recovered weapons from zanuka capture
IncHarvester?: boolean; // sent when recovered weapons from zanuka capture IncHarvester?: boolean; // sent when recovered weapons from zanuka capture

View File

@ -10,6 +10,7 @@ export interface IWorldState {
LiteSorties: ILiteSortie[]; LiteSorties: ILiteSortie[];
SyndicateMissions: ISyndicateMissionInfo[]; SyndicateMissions: ISyndicateMissionInfo[];
GlobalUpgrades: IGlobalUpgrade[]; GlobalUpgrades: IGlobalUpgrade[];
ActiveMissions: IFissure[];
NodeOverrides: INodeOverride[]; NodeOverrides: INodeOverride[];
EndlessXpChoices: IEndlessXpChoice[]; EndlessXpChoices: IEndlessXpChoice[];
SeasonInfo: { SeasonInfo: {
@ -71,6 +72,18 @@ export interface IGlobalUpgrade {
LocalizeDescTag: string; LocalizeDescTag: string;
} }
export interface IFissure {
_id: IOid;
Region: number;
Seed: number;
Activation: IMongoDate;
Expiry: IMongoDate;
Node: string;
MissionType: string;
Modifier: string;
Hard?: boolean;
}
export interface INodeOverride { export interface INodeOverride {
_id: IOid; _id: IOid;
Activation?: IMongoDate; Activation?: IMongoDate;

View File

@ -1,38 +1,38 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIGLzCCBRegAwIBAgIRAILIyLcitteoEGcJt1QBXvcwDQYJKoZIhvcNAQELBQAw MIIGMDCCBRigAwIBAgIQX4800cgswlDH/QexMSnnnjANBgkqhkiG9w0BAQsFADCB
gY8xCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO jzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
BgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE3MDUGA1UE A1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQD
AxMuU2VjdGlnbyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBD Ey5TZWN0aWdvIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB
QTAeFw0yNDA4MDIwMDAwMDBaFw0yNTA4MDIyMzU5NTlaMBcxFTATBgNVBAMMDCou MB4XDTI1MDMwNjAwMDAwMFoXDTI2MDMwNjIzNTk1OVowGDEWMBQGA1UEAwwNKi5m
dmlhdGxzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTToSjY YWtldGxzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMe42XWK
3aUIxjghIkikJfFExVwSEzIM2XaQNJE+SxQ6Cc+xUR5QJrMJnM/39sH5c5imMEUo HJuR7doFTX79zrEKfTlD2hjRIif3dHKJNTJNvZa52mIoHelP7RVUuFOhp7aZCNLh
2OnstCIaVMPx5ZPN+HXLsvmoVAe2/xYe7emnZ5ZFTUXPyqkzDRg0hkMJiWWo/Nmf IEzDyZObl8vwO6L2PVu5tbBEEoNixbpfhc8ZICEBuVo2UAhnJFcMJtuvtrCq+7ye
ypZfUJoz6hVkXwsgNFPTVuo7aECQFlZslh2HQVDOfBaNBxQBaOJ5vf6nllf/aLyB oczM/k/nh8FBz2WnLzWs4CZt1sa5knZXFmBmsHJQtQIC6vx7QzVcKGOlAosIEHSK
tZ74nlLynVYV9kYzISP4dUcxQ+D4HZgIxyOQfcN3EHUS1ZVaIp8hupOygF8zGQyJ X4nIz5fLgWSzor1Gay56j31PTk+qRvlPQM2aKiLWnlLfRED4zHJqLe94itu8llPX
uzFozzg5I59U+hT1yQG3FlwTBnP+sA0+hW0LBTbWSISm0If1SgHlUEqxLlosjuTG b6g+cLxxRKUpMqtG/15cDdBZwv40Dja7bmNfe1u4w2QCVLjvHVaVpNXbcRay/Mhn
BG45h9o2bAz9po0CAwEAAaOCAvswggL3MB8GA1UdIwQYMBaAFI2MXsRUrYrhd+mb M1w5LzDZmV58b18CAwEAAaOCAvwwggL4MB8GA1UdIwQYMBaAFI2MXsRUrYrhd+mb
+ZsF4bgBjWHhMB0GA1UdDgQWBBQ/OeA2gLbVIKIuIitYRqSRUWMP3TAOBgNVHQ8B +ZsF4bgBjWHhMB0GA1UdDgQWBBS6/x/N38wMJrQq/cE1oIcRERMonTAOBgNVHQ8B
Af8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB Af8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
BQUHAwIwSQYDVR0gBEIwQDA0BgsrBgEEAbIxAQICBzAlMCMGCCsGAQUFBwIBFhdo BQUHAwIwSQYDVR0gBEIwQDA0BgsrBgEEAbIxAQICBzAlMCMGCCsGAQUFBwIBFhdo
dHRwczovL3NlY3RpZ28uY29tL0NQUzAIBgZngQwBAgEwgYQGCCsGAQUFBwEBBHgw dHRwczovL3NlY3RpZ28uY29tL0NQUzAIBgZngQwBAgEwgYQGCCsGAQUFBwEBBHgw
djBPBggrBgEFBQcwAoZDaHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNB djBPBggrBgEFBQcwAoZDaHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNB
RG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNydDAjBggrBgEFBQcwAYYX RG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNydDAjBggrBgEFBQcwAYYX
aHR0cDovL29jc3Auc2VjdGlnby5jb20wIwYDVR0RBBwwGoIMKi52aWF0bHMuY29t aHR0cDovL29jc3Auc2VjdGlnby5jb20wJQYDVR0RBB4wHIINKi5mYWtldGxzLmNv
ggp2aWF0bHMuY29tMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdQDd3Mo0ldfh bYILZmFrZXRscy5jb20wggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB2AJaXZL9V
FgXnlTL6x5/4PRxQ39sAOhQSdgosrLvIKgAAAZEVLi9VAAAEAwBGMEQCIGiZNOV7 WJet90OHaDcIQnfp8DrV9qTzNm5GpD8PyqnGAAABlWsz5fgAAAQDAEcwRQIgTN7Y
IvcHKU7nEaxFgWPpUu2CxyULg1ueJTYwTT12AiAJWQv3RrqCtOJC7JEdztILs3Bn /mDqiD3RbGVLEOQK2wvXsboBolBRwGJFuFEsDScCIQCQ0qfb/0V8qqSxrkx/PiVS
an9s0Bf93uOE4C/LiAB3AA3h8jAr0w3BQGISCepVLvxHdHyx1+kw7w5CHrR+Tqo0 1lSn5gBEnQUiQOkefcnW0gB2ABmG1Mcoqm/+ugNveCpNAZGqzi1yMQ+uzl1wQS0l
AAABkRUuLxAAAAQDAEgwRgIhAOhlC+IpJV3uAaDCRXi6RZ+V8++QaLaTEtqFp2UP TMfUAAABlWsz5dAAAAQDAEcwRQIhAJnQJyrSCWWdi9Kyoa7XuMGyDKt183jJMY0E
yWeSAiEA8qtGDk1RE1VGwQQcJCf3CBYU5YTlsZNi7F6hEONLuzMAdwAS8U40vVNy 71abTuBOAiBC+WnK1esG6xr8aVGHRcc+1U/I7LiaG3LCRMYtCKrTGwB2AMs49xWJ
TIQGGcOPP3oT+Oe1YoeInG0wBYTr5YYmOgAAAZEVLi7kAAAEAwBIMEYCIQDWCnSm fIShRF9bwd37yW7ymlnNRwppBYWwyxTDFFjnAAABlWsz5f4AAAQDAEcwRQIhAJUs
N+2/xxo8bl3pbpNBEBZZIwnYPQW0A+SJ0+dboQIhANjH2L0xV/+rPuPMzK40vk3J 4PWDwyQJnCxCyEwFlFUY2uYQkGrQPA9f9Sw5Xk1fAiB63eQtZQGjvzvhOghy6z9a
1fWHLocLjpgaxGhsBAOzMA0GCSqGSIb3DQEBCwUAA4IBAQBcObVjc1zFdOER50ZF 8oGYbDfDQ/zfisMYO7rM6zANBgkqhkiG9w0BAQsFAAOCAQEAEHnSoeBbWiK3CS3a
mI+WyVF8t6nV6dm3zIDraLA4++zKUu9UKNJm9YPqLdPP7uTLHz6wuBNmyuWPdF0r px0BL+YXxRxdUcTMHgn5o+LlI9sWlpf+JLXmn7Z4QA6fAwT4k/Ue7xsmIq0OraDk
qAf4vsK3tcAds7kjK8injewEUCPG20mtNMUHyhlNEOJR2ySPPQ6Q+t+TtGAnimKa /pEVXWm1HO/9wUkGQg0DBi77BpfHircd7OWIMdt250Q8UAmZkOyhVgnwBcScqMwq
Zr86quYgYaJYhoEEXcbB9fMoDQYlJDzgT2DXvfM4cyoden2tYZ3gQS6ftiXacBe0 2T5CPaYvYGgYWx/qkIBv7JqhVbrP82rnF9b9ZUZ8GIE31chBmtMva9AsnAN5dmRw
WzFWYZ8mIP2Kb+D9tCapB9MVUzu3XJVy3S2FLQEWcWIvjnpad73a0/35i/nro6/k 81bVvPWXUfX30CYu5sxeWL06Zpy9nfJumxZri1SWXNTBjSvud2jsZ8tSCUAWLL/4
TSK+MKBEBaNZuHJ8ubCToo1BftnsS8HuEPTNe8W1hyc2YmT9f5YQP6HWB2rxjH42 ui3Vien9m2oMOpaA8xbS88ZTk9Alm/o5febEKJZUPlytQzij8gQpiovFw2v+Cdei
OTXh +tFXKw==
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIGEzCCA/ugAwIBAgIQfVtRJrR2uhHbdBYLvFMNpzANBgkqhkiG9w0BAQwFADCB MIIGEzCCA/ugAwIBAgIQfVtRJrR2uhHbdBYLvFMNpzANBgkqhkiG9w0BAQwFADCB

View File

@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDE06Eo2N2lCMY4 MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHuNl1ihybke3a
ISJIpCXxRMVcEhMyDNl2kDSRPksUOgnPsVEeUCazCZzP9/bB+XOYpjBFKNjp7LQi BU1+/c6xCn05Q9oY0SIn93RyiTUyTb2WudpiKB3pT+0VVLhToae2mQjS4SBMw8mT
GlTD8eWTzfh1y7L5qFQHtv8WHu3pp2eWRU1Fz8qpMw0YNIZDCYllqPzZn8qWX1Ca m5fL8Dui9j1bubWwRBKDYsW6X4XPGSAhAblaNlAIZyRXDCbbr7awqvu8nqHMzP5P
M+oVZF8LIDRT01bqO2hAkBZWbJYdh0FQznwWjQcUAWjieb3+p5ZX/2i8gbWe+J5S 54fBQc9lpy81rOAmbdbGuZJ2VxZgZrByULUCAur8e0M1XChjpQKLCBB0il+JyM+X
8p1WFfZGMyEj+HVHMUPg+B2YCMcjkH3DdxB1EtWVWiKfIbqTsoBfMxkMibsxaM84 y4Fks6K9Rmsueo99T05Pqkb5T0DNmioi1p5S30RA+Mxyai3veIrbvJZT12+oPnC8
OSOfVPoU9ckBtxZcEwZz/rANPoVtCwU21kiEptCH9UoB5VBKsS5aLI7kxgRuOYfa cUSlKTKrRv9eXA3QWcL+NA42u25jX3tbuMNkAlS47x1WlaTV23EWsvzIZzNcOS8w
NmwM/aaNAgMBAAECggEAEYK8bzxf96tAq0SzXqAP6heSsV7AS28eN7CbpKJUnp+N 2ZlefG9fAgMBAAECggEAT1Tti/LASks8300b60WFxG0WMJjzGMh5eMaiSpyVtNWM
OOePDnHWB46e31exoc82DAoY+EYqiiEvY2tRSD9wi8ZCyQQOz6w8kZUju42T3/ov aUKJrFOjDfnhgoeUcCPWKoG/L4Sc/+EFQMydDzTte120IasysEFZ2TZytAUdcZXZ
Ooy+06upXYU3sIQXv8YM7bjridbv+JHRQ27D8BRGamB6l0yRinQvkbLf8d9mOYkj XUMCDQNl5vCRTsJU7Q5u0t4YAGRCgMcsfTDKi8lISGiQKBHzN1CJ74Xm13rgOInd
P5yYrpMPV/mfgkCir/aBlGOzmI+CuOv7FdF9DIz2OehtPXOzbExuab4xOQ4NQrN9 lAc0wd5S89sL6RYmRTj1LvuZ95EHXHqQGdv0fIFEyP3pF1iPwcoTuIVEeICqnEvW
TfzWWS798D86e5uDx+Ab0pfege8IJvEBjU5ngZo3aeS/F5i2us+BXImu1P6IrYdb vd8CVO68eH3HFIwioqjp4qW3pxPZMhVq4161805uAMkoQlE+7MtEVenmP++1u1gM
ekXUo9VJPEHiD02iyLW/gFz3/AsWa3ztinXN0I069wKBgQD7yGPX6LG7MXlXEqL2 FjvAs3j9CZqOHZKcLlOtcGSwDlD++fCMMT4slLgLgQKBgQDy58E5nuYXdxlFQQk4
DuCrFsKzVC4z0c/cpPXO8Xb8rGzvHR7Oa0i5Bo7i5wRiVXS87HT25iZmB78yjKnI QccUKpyJ2aVXyp9xvTFBot/5Pik1SkuDzv2XU1OTxdxf3EongLy91nMJ2/6/39Je
bVmWA3tVWInx6fixbZUO7L4D/Q1Ddfin/DiXyNpAhKii0QgpD61P7HJnrfnwUar5 lf0/2MjzCtJ/lSzZ/zpJAu86UkBkWBAA5loGIof6OKedbEIgqpJqtK59S+j3ExO9
Vpwd2grnPNCbuILZxAZhtIXRnwKBgQDIH5hmyiIUAvrz+4UpE55ecxTMOkj0+Pgx eqa+uFrtt1UfaJG4A7TT+dIvIwKBgQDSfSOdSM5Dh3KsQHVnIWcIkzwTtlJlO+rG
79KpSjXfEIk5V7UmCSk1SusQWq8Ri9d6QqPcTptVhxmC/geolp9bCW14JdORbjNv 6rDEADxw6Kp8VIL/dq4Foe8yW4VqLVrWUuZsU6jzC9GdnyYi6VaqZ/iSUtGkBMOT
5+3JfAwgZJtbDP4l3GKf168fLQXzSpWCW3vT1lCBz4x4nNs2EudTdDCn5aUVLGEJ WTTYhqXlURaQ13jhqdwCZJRbVI72JbXn2OGEv8DgXnk//QKED/8VdKqAzCSr1t1f
v15Iz0dQUwKBgHuZh8n55SXrx5FDCNSZwRi796Bo9rVhjhTWtgR87NhlHKTVOsZC 3yfwei0AlQKBgD19KU66yKg7/+umEP1quUiDmOjUbaSRqFcUe3mQD356m9ffnMob
TFToL0Sb+776DHCh81kw6jC0JNv/yWkmpQ/LbcQbzrv/C6KuFLpa5Xy3wMcZJpPw BdrevxNzTNv/Wc4yKpUryic+x3gu4oQLF/annAbaQHsHejkdANYmpgRvedls6XAw
cSex5dI+TTqAOu1NUNsnS5IyCbw7mx8DsWfGHgweApovHa0hWbClGfwpAoGAfSt9 360Z5K4U1WlmVD8Mrs/QOTOCmdChxad7euZgqLPwat3ujKS2W3oljW1dAoGBAM4/
6DTfkcK3cilMhX+2236BcKe4ADlFC/7jtW0sOsQeAFbCf/LU6ndchVMjEwdzlA3g AB6lsDZLCfnuTxt2h1bHrh5CkAnR5AJ1BC+Ja6/WyvZ4eMOIroumWJKnStr3BgLr
bahg8eLZaxw2cBUdwRQpey+1n83csE7RZOeIsi4bGZ0LzWSF71I5P3eqtBxfXTSZ yAxtDSbZddNUljGvIdRnfBEkRXbJlDlVN4rSpMtF4S6bcz7rCUDu/M9g05Qs70j2
Q8tVeYv2YW5CkhTKyWDwGePCGHc0jqM6drHm+e8CgYEA+IkvplnK76zx3M579TpI IkPJAFzZNUWVzFlKs096uXbqkSQvrUq7ho8DqAThAoGBAL7Nrbr5LWcBgvwEhEla
M2ffiC2b12FtJMpgnNrk6HZ19o2mhbte/7wf9SHVqsRw4V7/n6rzBJ5wzEHOWre7 VRfYb0FUrDwLIrVWntJjW566/pVQQ4BmatsblLjlQYWk9MCIYXWZbnB+2fRx9yjQ
7PrkLgS0mY2SRfMmeT74m59dA8GpvhBUW/Xa4wlDtZkU2iIcDkKnYLjgrTKjlK86 Adggez7Dws/Mrh/wVudKgayHCy5Lgd8rYjNgC+VZf8XGrWX3QXMJ6UWAyQLTeoO7
ER+evzmHeTqYJzuK8Mfq93I= hToW9o9CQMIhaR43G8di1kjF
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@ -1097,7 +1097,5 @@
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLamp", "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLamp",
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampLarge", "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampLarge",
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampSmall", "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampSmall",
"/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem", "/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem"
"/Lotus/Types/Enemies/Orokin/OrokinMoaBipedAvatar",
"/Lotus/Types/Enemies/Grineer/AIWeek/Avatars/BeastMasterAvatar"
] ]

View File

@ -1,106 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5f456e01c96976e97d6b8016"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/FishmongerVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosOrokinFishAPartItem",
"PremiumPrice": [9, 9],
"Bin": "BIN_1",
"QuantityMultiplier": 10,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91b9"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishDPartItem",
"PremiumPrice": [17, 17],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91ba"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"PremiumPrice": [10, 10],
"Bin": "BIN_1",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91bb"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem",
"PremiumPrice": [6, 6],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91bc"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishAPartItem",
"PremiumPrice": [5, 5],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91bd"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosGenericSharedFishPartItem",
"PremiumPrice": [7, 7],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91be"
}
}
],
"PropertyTextHash": "6DF13A7FB573C25B4B4F989CBEFFC615",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,106 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "59d6e27ebcc718474eb17115"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/FishmongerVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayUncommonFishAPartItem",
"PremiumPrice": [14, 14],
"Bin": "BIN_1",
"QuantityMultiplier": 10,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9808"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem",
"PremiumPrice": [12, 12],
"Bin": "BIN_1",
"QuantityMultiplier": 10,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9809"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayCommonFishCPartItem",
"PremiumPrice": [8, 8],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e980a"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem",
"PremiumPrice": [7, 7],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e980b"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayCommonFishAPartItem",
"PremiumPrice": [10, 10],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e980c"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/BothCommonFishBPartItem",
"PremiumPrice": [8, 8],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e980d"
}
}
],
"PropertyTextHash": "CC3B9DAFB38F412998E90A41421A8986",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,106 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5b0de8556df82a56ea9bae82"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Solaris/FishmongerVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishThermalLaserItem",
"PremiumPrice": [15, 15],
"Bin": "BIN_1",
"QuantityMultiplier": 10,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9515"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishVenedoCaseItem",
"PremiumPrice": [8, 8],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9516"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/SolarisFishDissipatorCoilItem",
"PremiumPrice": [18, 18],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9517"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishExaBrainItem",
"PremiumPrice": [5, 5],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9518"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishAnoscopicSensorItem",
"PremiumPrice": [5, 5],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9519"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/GenericFishScrapItem",
"PremiumPrice": [5, 5],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e951a"
}
}
],
"PropertyTextHash": "946131D0CF5CDF7C2C03BB967DE0DF49",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -674,6 +674,10 @@
<input class="form-check-input" type="checkbox" id="noResourceExtractorDronesDamage" /> <input class="form-check-input" type="checkbox" id="noResourceExtractorDronesDamage" />
<label class="form-check-label" for="noResourceExtractorDronesDamage" data-loc="cheats_noResourceExtractorDronesDamage"></label> <label class="form-check-label" for="noResourceExtractorDronesDamage" data-loc="cheats_noResourceExtractorDronesDamage"></label>
</div> </div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="skipClanKeyCrafting" />
<label class="form-check-label" for="skipClanKeyCrafting" data-loc="cheats_skipClanKeyCrafting"></label>
</div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="noDojoRoomBuildStage" /> <input class="form-check-input" type="checkbox" id="noDojoRoomBuildStage" />
<label class="form-check-label" for="noDojoRoomBuildStage" data-loc="cheats_noDojoRoomBuildStage"></label> <label class="form-check-label" for="noDojoRoomBuildStage" data-loc="cheats_noDojoRoomBuildStage"></label>

View File

@ -375,6 +375,7 @@ function fetchItemList() {
} }
fetchItemList(); fetchItemList();
// Assumes that caller revalidates authz
function updateInventory() { function updateInventory() {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => { req.done(data => {
@ -487,25 +488,27 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
if (item.XP < maxXP) { revalidateAuthz(() => {
addGearExp(category, item.ItemId.$oid, maxXP - item.XP); if (item.XP < maxXP) {
} addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
if ("exalted" in itemMap[item.ItemType]) { }
for (const exaltedType of itemMap[item.ItemType].exalted) { if ("exalted" in itemMap[item.ItemType]) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType); for (const exaltedType of itemMap[item.ItemType].exalted) {
if (exaltedItem) { const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
const exaltedCap = if (exaltedItem) {
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000; const exaltedCap =
if (exaltedItem.XP < exaltedCap) { itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
addGearExp( if (exaltedItem.XP < exaltedCap) {
"SpecialItems", addGearExp(
exaltedItem.ItemId.$oid, "SpecialItems",
exaltedCap - exaltedItem.XP exaltedItem.ItemId.$oid,
); exaltedCap - exaltedItem.XP
);
}
} }
} }
} }
} });
}; };
a.title = loc("code_maxRank"); a.title = loc("code_maxRank");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
@ -1229,76 +1232,22 @@ function addMissingEvolutionProgress() {
} }
function maxRankAllEvolutions() { function maxRankAllEvolutions() {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); revalidateAuthz(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => {
const requests = [];
req.done(data => { data.EvolutionProgress.forEach(item => {
const requests = []; if (item.Rank < 5) {
requests.push({
data.EvolutionProgress.forEach(item => { ItemType: item.ItemType,
if (item.Rank < 5) { Rank: 5
requests.push({ });
ItemType: item.ItemType, }
Rank: 5
});
}
});
if (Object.keys(requests).length > 0) {
return setEvolutionProgress(requests);
}
toast(loc("code_noEquipmentToRankUp"));
});
}
function maxRankAllEquipment(categories) {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => {
window.itemListPromise.then(itemMap => {
const batchData = {};
categories.forEach(category => {
data[category].forEach(item => {
const maxXP =
category === "Suits" ||
category === "SpaceSuits" ||
category === "Sentinels" ||
category === "Hoverboards"
? 1_600_000
: 800_000;
if (item.XP < maxXP) {
if (!batchData[category]) {
batchData[category] = [];
}
batchData[category].push({
ItemId: { $oid: item.ItemId.$oid },
XP: maxXP
});
}
if (category === "Suits") {
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data["SpecialItems"].find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
batchData["SpecialItems"] ??= [];
batchData["SpecialItems"].push({
ItemId: { $oid: exaltedItem.ItemId.$oid },
XP: exaltedCap
});
}
}
}
}
}
});
}); });
if (Object.keys(batchData).length > 0) { if (Object.keys(requests).length > 0) {
return sendBatchGearExp(batchData); return setEvolutionProgress(requests);
} }
toast(loc("code_noEquipmentToRankUp")); toast(loc("code_noEquipmentToRankUp"));
@ -1306,6 +1255,64 @@ function maxRankAllEquipment(categories) {
}); });
} }
function maxRankAllEquipment(categories) {
revalidateAuthz(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => {
window.itemListPromise.then(itemMap => {
const batchData = {};
categories.forEach(category => {
data[category].forEach(item => {
const maxXP =
category === "Suits" ||
category === "SpaceSuits" ||
category === "Sentinels" ||
category === "Hoverboards"
? 1_600_000
: 800_000;
if (item.XP < maxXP) {
if (!batchData[category]) {
batchData[category] = [];
}
batchData[category].push({
ItemId: { $oid: item.ItemId.$oid },
XP: maxXP
});
}
if (category === "Suits") {
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data["SpecialItems"].find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
batchData["SpecialItems"] ??= [];
batchData["SpecialItems"].push({
ItemId: { $oid: exaltedItem.ItemId.$oid },
XP: exaltedCap
});
}
}
}
}
}
});
});
if (Object.keys(batchData).length > 0) {
return sendBatchGearExp(batchData);
}
toast(loc("code_noEquipmentToRankUp"));
});
});
});
}
// Assumes that caller revalidates authz
function addGearExp(category, oid, xp) { function addGearExp(category, oid, xp) {
const data = {}; const data = {};
data[category] = [ data[category] = [
@ -1314,16 +1321,14 @@ function addGearExp(category, oid, xp) {
XP: xp XP: xp
} }
]; ];
revalidateAuthz(() => { $.post({
$.post({ url: "/custom/addXp?" + window.authz,
url: "/custom/addXp?" + window.authz, contentType: "application/json",
contentType: "application/json", data: JSON.stringify(data)
data: JSON.stringify(data) }).done(function () {
}).done(function () { if (category != "SpecialItems") {
if (category != "SpecialItems") { updateInventory();
updateInventory(); }
}
});
}); });
} }
@ -1598,32 +1603,34 @@ function doAcquireMod() {
const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id); const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id);
function doChangeSettings() { function doChangeSettings() {
fetch("/custom/config?" + window.authz) revalidateAuthz(() => {
.then(response => response.json()) fetch("/custom/config?" + window.authz)
.then(json => { .then(response => response.json())
for (const i of uiConfigs) { .then(json => {
var x = document.getElementById(i); for (const i of uiConfigs) {
if (x != null) { var x = document.getElementById(i);
if (x.type == "checkbox") { if (x != null) {
if (x.checked === true) { if (x.type == "checkbox") {
json[i] = true; if (x.checked === true) {
} else { json[i] = true;
json[i] = false; } else {
json[i] = false;
}
} else if (x.type == "number") {
json[i] = parseInt(x.value);
} }
} else if (x.type == "number") {
json[i] = parseInt(x.value);
} }
} }
} $.post({
$.post({ url: "/custom/config?" + window.authz,
url: "/custom/config?" + window.authz, contentType: "text/plain",
contentType: "text/plain", data: JSON.stringify(json, null, 2)
data: JSON.stringify(json, null, 2) }).then(() => {
}).then(() => { // A few cheats affect the inventory response which in turn may change what values we need to show
// A few cheats affect the inventory response which in turn may change what values we need to show updateInventory();
updateInventory(); });
}); });
}); });
} }
// Cheats route // Cheats route
@ -1876,33 +1883,39 @@ function doChangeSupportedSyndicate() {
} }
function doAddCurrency(currency) { function doAddCurrency(currency) {
$.post({ revalidateAuthz(() => {
url: "/custom/addCurrency?" + window.authz, $.post({
contentType: "application/json", url: "/custom/addCurrency?" + window.authz,
data: JSON.stringify({ contentType: "application/json",
currency, data: JSON.stringify({
delta: document.getElementById(currency + "-delta").valueAsNumber currency,
}) delta: document.getElementById(currency + "-delta").valueAsNumber
}).then(function () { })
updateInventory(); }).then(function () {
updateInventory();
});
}); });
} }
function doQuestUpdate(operation, itemType) { function doQuestUpdate(operation, itemType) {
$.post({ revalidateAuthz(() => {
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType, $.post({
contentType: "application/json" url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
}).then(function () { contentType: "application/json"
updateInventory(); }).then(function () {
updateInventory();
});
}); });
} }
function doBulkQuestUpdate(operation) { function doBulkQuestUpdate(operation) {
$.post({ revalidateAuthz(() => {
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation, $.post({
contentType: "application/json" url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
}).then(function () { contentType: "application/json"
updateInventory(); }).then(function () {
updateInventory();
});
}); });
} }

View File

@ -34,8 +34,8 @@ dict = {
code_rerollsNumber: `Anzahl der Umrollversuche`, code_rerollsNumber: `Anzahl der Umrollversuche`,
code_viewStats: `Statistiken anzeigen`, code_viewStats: `Statistiken anzeigen`,
code_rank: `Rang`, code_rank: `Rang`,
code_rankUp: `[UNTRANSLATED] Rank up`, code_rankUp: `Rang erhöhen`,
code_rankDown: `[UNTRANSLATED] Rank down`, code_rankDown: `Rang verringern`,
code_count: `Anzahl`, code_count: `Anzahl`,
code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`, code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`,
code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
@ -84,23 +84,23 @@ dict = {
inventory_sentinelWeapons: `Wächter-Waffen`, inventory_sentinelWeapons: `Wächter-Waffen`,
inventory_operatorAmps: `Verstärker`, inventory_operatorAmps: `Verstärker`,
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bestien`, inventory_kubrowPets: `Bestien`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`, inventory_evolutionProgress: `Incarnon-Entwicklungsfortschritte`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`, inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`, inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`, inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`, inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`,
inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`, inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`,
inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`, inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`, inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`,
inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`, inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`, inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`, inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
inventory_bulkRankUpSpaceWeapons: `Alle Archwing-Waffen auf Max. Rang`, inventory_bulkRankUpSpaceWeapons: `Alle Archwing-Waffen auf Max. Rang`,
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`, inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`, inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`, quests_completeAll: `Alle Quests abschließen`,
@ -120,9 +120,9 @@ dict = {
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`, mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
mods_rivens: `Rivens`, mods_rivens: `Rivens`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`, mods_addMissingUnrankedMods: `Fehlende Mods ohne Rang hinzufügen`,
mods_removeUnranked: `Mods ohne Rang entfernen`, mods_removeUnranked: `Mods ohne Rang entfernen`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`, mods_addMissingMaxRankMods: `Fehlende Mods mit Max. Rang hinzufügen`,
cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`, cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`,
cheats_server: `Server`, cheats_server: `Server`,
cheats_skipTutorial: `Tutorial überspringen`, cheats_skipTutorial: `Tutorial überspringen`,
@ -134,7 +134,7 @@ dict = {
cheats_infiniteEndo: `Unendlich Endo`, cheats_infiniteEndo: `Unendlich Endo`,
cheats_infiniteRegalAya: `Unendlich Reines Aya`, cheats_infiniteRegalAya: `Unendlich Reines Aya`,
cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`, cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`, cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`, cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`, cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`,
cheats_unlockAllFlavourItems: `Alle <abbr title=\"Animationssets, Glyphen, Farbpaletten usw.\">Sammlerstücke</abbr> freischalten`, cheats_unlockAllFlavourItems: `Alle <abbr title=\"Animationssets, Glyphen, Farbpaletten usw.\">Sammlerstücke</abbr> freischalten`,
@ -154,6 +154,7 @@ dict = {
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`, cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`, cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
cheats_skipClanKeyCrafting: `Clan-Schlüsselherstellung überspringen`,
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`, cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,
cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`, cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`,
cheats_fastDojoRoomDestruction: `Schnelle Dojo-Raum-Zerstörung`, cheats_fastDojoRoomDestruction: `Schnelle Dojo-Raum-Zerstörung`,

View File

@ -83,7 +83,7 @@ dict = {
inventory_sentinelWeapons: `Sentinel Weapons`, inventory_sentinelWeapons: `Sentinel Weapons`,
inventory_operatorAmps: `Amps`, inventory_operatorAmps: `Amps`,
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moas`,
inventory_kubrowPets: `Beasts`, inventory_kubrowPets: `Beasts`,
inventory_evolutionProgress: `Incarnon Evolution Progress`, inventory_evolutionProgress: `Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Add Missing Warframes`, inventory_bulkAddSuits: `Add Missing Warframes`,
@ -153,6 +153,7 @@ dict = {
cheats_noKimCooldowns: `No KIM Cooldowns`, cheats_noKimCooldowns: `No KIM Cooldowns`,
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`, cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`, cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`,
cheats_skipClanKeyCrafting: `Skip Clan Key Crafting`,
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`, cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
cheats_noDojoDecoBuildStage: `No Dojo Deco Build Stage`, cheats_noDojoDecoBuildStage: `No Dojo Deco Build Stage`,
cheats_fastDojoRoomDestruction: `Fast Dojo Room Destruction`, cheats_fastDojoRoomDestruction: `Fast Dojo Room Destruction`,

View File

@ -84,7 +84,7 @@ dict = {
inventory_sentinelWeapons: `Armas de centinela`, inventory_sentinelWeapons: `Armas de centinela`,
inventory_operatorAmps: `Amps`, inventory_operatorAmps: `Amps`,
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bestias`, inventory_kubrowPets: `Bestias`,
inventory_evolutionProgress: `Progreso de evolución Incarnon`, inventory_evolutionProgress: `Progreso de evolución Incarnon`,
inventory_bulkAddSuits: `Agregar Warframes faltantes`, inventory_bulkAddSuits: `Agregar Warframes faltantes`,
@ -154,6 +154,7 @@ dict = {
cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`, cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`,
cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`, cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`,
cheats_noResourceExtractorDronesDamage: `Sin daño a los drones extractores de recursos`, cheats_noResourceExtractorDronesDamage: `Sin daño a los drones extractores de recursos`,
cheats_skipClanKeyCrafting: `Saltar la fabricación de la llave de clan`,
cheats_noDojoRoomBuildStage: `Sin etapa de construcción de sala del dojo`, cheats_noDojoRoomBuildStage: `Sin etapa de construcción de sala del dojo`,
cheats_noDojoDecoBuildStage: `Sin etapa de construcción de decoraciones del dojo`, cheats_noDojoDecoBuildStage: `Sin etapa de construcción de decoraciones del dojo`,
cheats_fastDojoRoomDestruction: `Destrucción rápida de salas del dojo`, cheats_fastDojoRoomDestruction: `Destrucción rápida de salas del dojo`,

View File

@ -84,7 +84,7 @@ dict = {
inventory_sentinelWeapons: `Armes de sentinelles`, inventory_sentinelWeapons: `Armes de sentinelles`,
inventory_operatorAmps: `Amplificateurs`, inventory_operatorAmps: `Amplificateurs`,
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bêtes`, inventory_kubrowPets: `Bêtes`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`, inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
@ -154,6 +154,7 @@ dict = {
cheats_noKimCooldowns: `Aucun cooldown sur le KIM`, cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`, cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`, cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`,
cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`,
cheats_noDojoRoomBuildStage: `Aucune attente (construction des salles)`, cheats_noDojoRoomBuildStage: `Aucune attente (construction des salles)`,
cheats_noDojoDecoBuildStage: `Aucune attente (construction des décorations)`, cheats_noDojoDecoBuildStage: `Aucune attente (construction des décorations)`,
cheats_fastDojoRoomDestruction: `Destruction de salle instantanée (Dojo)`, cheats_fastDojoRoomDestruction: `Destruction de salle instantanée (Dojo)`,

View File

@ -154,6 +154,7 @@ dict = {
cheats_noKimCooldowns: `Чаты KIM без кулдауна`, cheats_noKimCooldowns: `Чаты KIM без кулдауна`,
cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`, cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`,
cheats_noResourceExtractorDronesDamage: `Без урона по дронам-сборщикам`, cheats_noResourceExtractorDronesDamage: `Без урона по дронам-сборщикам`,
cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`,
cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`, cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`,
cheats_noDojoDecoBuildStage: `Мгновенное Строительтво Декораций Додзё`, cheats_noDojoDecoBuildStage: `Мгновенное Строительтво Декораций Додзё`,
cheats_fastDojoRoomDestruction: `Мгновенные Уничтожение Комнат Додзё`, cheats_fastDojoRoomDestruction: `Мгновенные Уничтожение Комнат Додзё`,

View File

@ -154,6 +154,7 @@ dict = {
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`, cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
cheats_instantResourceExtractorDrones: `即时资源采集无人机`, cheats_instantResourceExtractorDrones: `即时资源采集无人机`,
cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`, cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`,
cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`,
cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`, cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`,
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`, cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
cheats_fastDojoRoomDestruction: `快速拆除道场房间`, cheats_fastDojoRoomDestruction: `快速拆除道场房间`,