mirror of
https://github.com/sp-tarkov/server.git
synced 2025-02-13 09:50:43 -05:00
update 3.9.0 with 3.8.1 changes (!289)
Co-authored-by: Refringe <me@refringe.com> Co-authored-by: Dev <dev@dev.sp-tarkov.com> Co-authored-by: Terkoiz <terkoiz@spt.dev> Co-authored-by: Refringe <refringe@noreply.dev.sp-tarkov.com> Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com> Reviewed-on: SPT-AKI/Server#289
This commit is contained in:
parent
dedb47eb14
commit
687436ab8b
58
.gitea/workflows/run-lint.yaml
Normal file
58
.gitea/workflows/run-lint.yaml
Normal file
@ -0,0 +1,58 @@
|
||||
name: Run Code Linter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: '*'
|
||||
pull_request:
|
||||
branches: '*'
|
||||
|
||||
jobs:
|
||||
biome:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: refringe/spt-build-node:1.0.7
|
||||
steps:
|
||||
- name: Clone
|
||||
run: |
|
||||
rm -rf /workspace/SPT-AKI/Build/server
|
||||
git clone https://dev.sp-tarkov.com/SPT-AKI/Server.git --branch master /workspace/SPT-AKI/Build/server
|
||||
|
||||
cd /workspace/SPT-AKI/Build/server
|
||||
git checkout ${GITHUB_SHA}
|
||||
shell: bash
|
||||
|
||||
- name: Pull LFS Files
|
||||
run: |
|
||||
cd /workspace/SPT-AKI/Build/server
|
||||
git lfs pull
|
||||
git lfs ls-files
|
||||
shell: bash
|
||||
|
||||
- name: Cache NPM Dependencies
|
||||
id: cache-npm-dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /workspace/SPT-AKI/Build/server/project/node_modules
|
||||
key: npm-dependencies-${{ hashFiles('/workspace/SPT-AKI/Build/server/project/package.json') }}
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
if: steps.cache-npm-dependencies.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd /workspace/SPT-AKI/Build/server/project
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
shell: bash
|
||||
|
||||
- name: Run Linter
|
||||
id: run-tests
|
||||
run: |
|
||||
cd /workspace/SPT-AKI/Build/server/project
|
||||
npm run lint
|
||||
shell: bash
|
||||
|
||||
- name: Fix Instructions
|
||||
if: failure() && steps.run-tests.outcome == 'failure'
|
||||
run: |
|
||||
echo -e "Code linting has failed. The linter has been configured to look for coding errors, defects, and questionable patterns. Please look into resolving these errors. The linter may be able to resolve some of these issues automatically. You can launch the automatic fixer by running the following command from within the 'project' directory. Anything not resolved by running this command must be resolved manually.\n\nnpm run lint:fix\n"
|
||||
echo -e "Consistency is professionalism.™"
|
||||
shell: bash
|
59
.gitea/workflows/run-style.yaml
Normal file
59
.gitea/workflows/run-style.yaml
Normal file
@ -0,0 +1,59 @@
|
||||
name: Check Code Style
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: '*'
|
||||
pull_request:
|
||||
branches: '*'
|
||||
|
||||
jobs:
|
||||
dprint:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: refringe/spt-build-node:1.0.7
|
||||
steps:
|
||||
- name: Clone
|
||||
run: |
|
||||
rm -rf /workspace/SPT-AKI/Build/server
|
||||
git clone https://dev.sp-tarkov.com/SPT-AKI/Server.git --branch master /workspace/SPT-AKI/Build/server
|
||||
|
||||
cd /workspace/SPT-AKI/Build/server
|
||||
git checkout ${GITHUB_SHA}
|
||||
shell: bash
|
||||
|
||||
- name: Pull LFS Files
|
||||
run: |
|
||||
cd /workspace/SPT-AKI/Build/server
|
||||
git lfs pull
|
||||
git lfs ls-files
|
||||
shell: bash
|
||||
|
||||
- name: Cache NPM Dependencies
|
||||
id: cache-npm-dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /workspace/SPT-AKI/Build/server/project/node_modules
|
||||
key: npm-dependencies-${{ hashFiles('/workspace/SPT-AKI/Build/server/project/package.json') }}
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
if: steps.cache-npm-dependencies.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd /workspace/SPT-AKI/Build/server/project
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
shell: bash
|
||||
|
||||
- name: Check Code Style
|
||||
id: check-code-style
|
||||
run: |
|
||||
cd /workspace/SPT-AKI/Build/server/project
|
||||
npm run style
|
||||
shell: bash
|
||||
|
||||
- name: Fix Instructions
|
||||
if: failure() && steps.check-code-style.outcome == 'failure'
|
||||
run: |
|
||||
echo -e "The code style check has failed. To fix this, please ensure your code adheres to the project's style guidelines. You can automatically format the project code by running the following command from within the 'project' directory.\n\nnpm run style:fix\n"
|
||||
echo -e "To automatically format code on-save in your IDE, please install the recommended VSCode plugins listed within the 'project/Server.code-workspace' file.\n"
|
||||
echo -e "Thank you for keeping our house clean. ♥"
|
||||
shell: bash
|
58
.gitea/workflows/run-test.yaml
Normal file
58
.gitea/workflows/run-test.yaml
Normal file
@ -0,0 +1,58 @@
|
||||
name: Run Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: '*'
|
||||
pull_request:
|
||||
branches: '*'
|
||||
|
||||
jobs:
|
||||
vitest:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: refringe/spt-build-node:1.0.7
|
||||
steps:
|
||||
- name: Clone
|
||||
run: |
|
||||
rm -rf /workspace/SPT-AKI/Build/server
|
||||
git clone https://dev.sp-tarkov.com/SPT-AKI/Server.git --branch master /workspace/SPT-AKI/Build/server
|
||||
|
||||
cd /workspace/SPT-AKI/Build/server
|
||||
git checkout ${GITHUB_SHA}
|
||||
shell: bash
|
||||
|
||||
- name: Pull LFS Files
|
||||
run: |
|
||||
cd /workspace/SPT-AKI/Build/server
|
||||
git lfs pull
|
||||
git lfs ls-files
|
||||
shell: bash
|
||||
|
||||
- name: Cache NPM Dependencies
|
||||
id: cache-npm-dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /workspace/SPT-AKI/Build/server/project/node_modules
|
||||
key: npm-dependencies-${{ hashFiles('/workspace/SPT-AKI/Build/server/project/package.json') }}
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
if: steps.cache-npm-dependencies.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd /workspace/SPT-AKI/Build/server/project
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
shell: bash
|
||||
|
||||
- name: Run Tests
|
||||
id: run-tests
|
||||
run: |
|
||||
cd /workspace/SPT-AKI/Build/server/project
|
||||
npm run test
|
||||
shell: bash
|
||||
|
||||
- name: Fix Instructions
|
||||
if: failure() && steps.run-tests.outcome == 'failure'
|
||||
run: |
|
||||
echo -e "Automated tests have failed. This could point to an issue with the commited code, or an updated test that has yet to be updated. Please look into resolving these test failures. The testing suite has a GUI to aid in writing tests. You can launch this by running the following command from within the 'project' directory.\n\nnpm run test:ui\n"
|
||||
echo -e "A test written today is a bug prevented tomorrow.™"
|
||||
shell: bash
|
@ -324,7 +324,9 @@
|
||||
"5d08d21286f774736e7c94c3": 1,
|
||||
"5c94bbff86f7747ee735c08f": 1
|
||||
},
|
||||
"bosssanitar": {},
|
||||
"bosssanitar": {
|
||||
"5efde6b4f5448336730dbd61": 1
|
||||
},
|
||||
"bosstagilla": {},
|
||||
"bossknight": {},
|
||||
"bosszryachiy": {},
|
||||
@ -387,8 +389,12 @@
|
||||
"pmcbot": {
|
||||
"60098ad7c2240c0fe85c570a": 2
|
||||
},
|
||||
"arenafighterevent": {},
|
||||
"arenafighter": {},
|
||||
"arenafighterevent": {
|
||||
"5734758f24597738025ee253": 1
|
||||
},
|
||||
"arenafighter": {
|
||||
"5734758f24597738025ee253": 1
|
||||
},
|
||||
"crazyassaultevent": {},
|
||||
"assaultgroup": {},
|
||||
"gifter": {},
|
||||
|
@ -5,6 +5,7 @@
|
||||
"serverName": "SPT Server",
|
||||
"profileSaveIntervalSeconds": 15,
|
||||
"sptFriendNickname": "SPT",
|
||||
"allowProfileWipe": true,
|
||||
"bsgLogging": {
|
||||
"verbosity": 6,
|
||||
"sendToServer": false
|
||||
|
@ -1,9 +1,12 @@
|
||||
{
|
||||
"runIntervalSeconds": 10,
|
||||
"hoursForSkillCrafting": 28800,
|
||||
"runIntervalValues": {
|
||||
"runIntervalValues": {
|
||||
"inRaid": 60,
|
||||
"outOfRaid": 10
|
||||
},
|
||||
"expCraftAmount": 10
|
||||
"expCraftAmount": 10,
|
||||
"overrideCraftTimeSeconds": -1,
|
||||
"overrideBuildTimeSeconds": -1,
|
||||
"updateProfileHideoutWhenActiveWithinMinutes": 90
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"port": 6969,
|
||||
"backendIp": "127.0.0.1",
|
||||
"backendPort": 6969,
|
||||
"webSocketPingDelayMs": 90000,
|
||||
"logRequests": true,
|
||||
"serverImagePathOverride": {}
|
||||
|
@ -11,8 +11,7 @@
|
||||
"randomTime": false
|
||||
},
|
||||
"save": {
|
||||
"loot": true,
|
||||
"durability": true
|
||||
"loot": true
|
||||
},
|
||||
"carExtracts": [
|
||||
"Dorms V-Ex",
|
||||
|
@ -50,6 +50,7 @@
|
||||
"5580239d4bdc2de7118b4583"
|
||||
],
|
||||
"rewardItemBlacklist": [
|
||||
"58ac60eb86f77401897560ff",
|
||||
"5e997f0b86f7741ac73993e2",
|
||||
"5b44abe986f774283e2e3512",
|
||||
"5e99711486f7744bfc4af328",
|
||||
|
@ -1146,6 +1146,7 @@
|
||||
"minFillStaticMagazinePercent": 50,
|
||||
"allowDuplicateItemsInStaticContainers": true,
|
||||
"magazineLootHasAmmoChancePercent": 50,
|
||||
"staticMagazineLootHasAmmoChancePercent": 0,
|
||||
"looseLootBlacklist": {},
|
||||
"scavRaidTimeSettings": {
|
||||
"settings": {
|
||||
|
@ -157,7 +157,7 @@
|
||||
"543be6564bdc2df4348b4568": 0,
|
||||
"5448ecbe4bdc2d60728b4568": 0,
|
||||
"5671435f4bdc2d96058b4569": 0,
|
||||
"543be5cb4bdc2deb348b4568": 3,
|
||||
"543be5cb4bdc2deb348b4568": 5,
|
||||
"5448e53e4bdc2d60728b4567": 7
|
||||
},
|
||||
"preventDuplicateOffersOfCategory": [
|
||||
@ -319,6 +319,7 @@
|
||||
"left_side_plate": 75,
|
||||
"right_side_plate": 75
|
||||
},
|
||||
"ammoMaxPenLimit": 20,
|
||||
"blacklistSeasonalItems": true,
|
||||
"blacklist": [
|
||||
"5c164d2286f774194c5e69fa",
|
||||
@ -342,7 +343,8 @@
|
||||
"5a341c4086f77401f2541505",
|
||||
"5422acb9af1c889c16000029",
|
||||
"64d0b40fbe2eed70e254e2d4",
|
||||
"5fc22d7c187fea44d52eda44"
|
||||
"5fc22d7c187fea44d52eda44",
|
||||
"646372518610c40fc20204e8"
|
||||
],
|
||||
"coopExtractGift": {
|
||||
"sendGift": true,
|
||||
@ -351,7 +353,11 @@
|
||||
"5da89b3a86f7742f9026cb83 0"
|
||||
],
|
||||
"giftExpiryHours": 168,
|
||||
"presetCount": {
|
||||
"weaponPresetCount": {
|
||||
"min": 0,
|
||||
"max": 0
|
||||
},
|
||||
"armorPresetCount": {
|
||||
"min": 0,
|
||||
"max": 0
|
||||
},
|
||||
|
@ -2110,6 +2110,22 @@
|
||||
"2": 0
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
|
@ -2347,6 +2347,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 30,
|
||||
@ -2355,6 +2363,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 8,
|
||||
|
@ -2188,7 +2188,7 @@
|
||||
"Basuro",
|
||||
"Bepis",
|
||||
"Baliston",
|
||||
"Pessin",
|
||||
"Crow",
|
||||
"Aki-chan",
|
||||
"Fin",
|
||||
"Gatsu66",
|
||||
@ -2481,9 +2481,11 @@
|
||||
"Brin",
|
||||
"Belette",
|
||||
"Agnotology",
|
||||
"All_Heil_Lord_Ppepe",
|
||||
"ixcetotis",
|
||||
"btdc00"
|
||||
"All_Heil_Lord_Pepe",
|
||||
"ixcetotis",
|
||||
"btdc00",
|
||||
"Bnuy",
|
||||
"Choccy"
|
||||
],
|
||||
"generation": {
|
||||
"items": {
|
||||
@ -2508,11 +2510,27 @@
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 6,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 6,
|
||||
"1": 5,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 4,
|
||||
"0": 2,
|
||||
"1": 6,
|
||||
"2": 5,
|
||||
"3": 2,
|
||||
"4": 1
|
||||
@ -2988,9 +3006,9 @@
|
||||
"5ac66d9b5acfc4001633997a": 5,
|
||||
"5ae08f0a5acfc408fb1398a1": 4,
|
||||
"5b0bbe4e5acfc40dc528a72d": 4,
|
||||
"5ba26383d4351e00334c93d9": 5,
|
||||
"5ba26383d4351e00334c93d9": 4,
|
||||
"5bb2475ed4351e00853264e3": 4,
|
||||
"5bd70322209c4d00d7167b8f": 5,
|
||||
"5bd70322209c4d00d7167b8f": 4,
|
||||
"5beed0f50db834001c062b12": 3,
|
||||
"5bf3e03b0db834001d2c4a9c": 5,
|
||||
"5bf3e0490db83400196199af": 5,
|
||||
|
@ -2302,6 +2302,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2310,6 +2318,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2038,6 +2038,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2046,6 +2054,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2029,6 +2029,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2036,6 +2044,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2317,6 +2317,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2324,6 +2332,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2019,6 +2019,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2026,6 +2034,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2167,6 +2167,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2175,6 +2183,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2070,6 +2070,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2078,6 +2086,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2322,6 +2322,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2330,6 +2338,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2114,6 +2114,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2122,6 +2130,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2113,6 +2113,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2121,6 +2129,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -1946,6 +1946,22 @@
|
||||
"healing": {
|
||||
"max": 2,
|
||||
"min": 1
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"max": 3,
|
||||
|
@ -2024,6 +2024,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2032,6 +2040,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2365,6 +2365,22 @@
|
||||
"2": 0
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
|
@ -2391,6 +2391,22 @@
|
||||
"2": 0
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
|
@ -2254,6 +2254,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2261,6 +2269,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2053,6 +2053,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2061,6 +2069,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2085,6 +2085,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2093,6 +2101,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2253,6 +2253,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 9,
|
||||
@ -2263,6 +2271,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2173,6 +2173,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2181,6 +2189,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2173,6 +2173,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2181,6 +2189,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2044,6 +2044,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2052,6 +2060,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2212,6 +2212,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2220,6 +2228,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2214,6 +2214,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2222,6 +2230,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2217,6 +2217,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2225,6 +2233,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2099,6 +2099,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2107,6 +2115,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2232,6 +2232,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2240,6 +2248,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2245,6 +2245,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2253,6 +2261,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2144,6 +2144,22 @@
|
||||
"2": 0
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
|
@ -2003,6 +2003,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2011,6 +2019,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2064,6 +2064,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2072,6 +2080,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2302,6 +2302,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2310,6 +2318,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2341,10 +2357,10 @@
|
||||
},
|
||||
"pocketLoot": {
|
||||
"weights": {
|
||||
"0": 3,
|
||||
"1": 10,
|
||||
"0": 10,
|
||||
"1": 35,
|
||||
"2": 3,
|
||||
"3": 1,
|
||||
"3": 2,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": []
|
||||
|
@ -2004,33 +2004,108 @@
|
||||
],
|
||||
"generation": {
|
||||
"items": {
|
||||
"backpackLoot": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 1,
|
||||
"2": 2,
|
||||
"3": 1,
|
||||
"4": 1,
|
||||
"5": 1,
|
||||
"6": 1,
|
||||
"7": 0
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"max": 1,
|
||||
"min": 0
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 2,
|
||||
"2": 0
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"max": 5,
|
||||
"min": 0
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 2,
|
||||
"2": 1,
|
||||
"3": 1,
|
||||
"4": 0,
|
||||
"5": 0
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"healing": {
|
||||
"max": 2,
|
||||
"min": 1
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 2,
|
||||
"2": 1
|
||||
},
|
||||
"looseLoot": {
|
||||
"max": 3,
|
||||
"min": 0
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"max": 4,
|
||||
"min": 2
|
||||
"weights": {
|
||||
"0": 0,
|
||||
"1": 0,
|
||||
"2": 1,
|
||||
"3": 3,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"pocketLoot": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 6,
|
||||
"2": 3,
|
||||
"3": 1,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"specialItems": {
|
||||
"max": 0,
|
||||
"min": 0
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 0
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"max": 1,
|
||||
"min": 0
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1,
|
||||
"2": 0
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"vestLoot": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 1,
|
||||
"2": 2,
|
||||
"3": 1,
|
||||
"4": 0,
|
||||
"5": 0,
|
||||
"6": 0
|
||||
},
|
||||
"whitelist": []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2119,6 +2119,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2127,6 +2135,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2085,6 +2085,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2093,6 +2101,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2101,6 +2101,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2109,6 +2117,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2112,6 +2112,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2120,6 +2128,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -2146,6 +2146,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
@ -2154,6 +2162,14 @@
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
|
@ -1938,6 +1938,22 @@
|
||||
"drugs": {
|
||||
"max": 1,
|
||||
"min": 0
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 10,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"max": 5,
|
||||
|
@ -2185,7 +2185,7 @@
|
||||
"Basuro",
|
||||
"Bepis",
|
||||
"Baliston",
|
||||
"Pessin",
|
||||
"Crow",
|
||||
"Aki-chan",
|
||||
"Fin",
|
||||
"Gatsu66",
|
||||
@ -2478,9 +2478,11 @@
|
||||
"Brin",
|
||||
"Belette",
|
||||
"Agnotology",
|
||||
"All_Heil_Lord_Ppepe",
|
||||
"ixcetotis",
|
||||
"btdc00"
|
||||
"All_Heil_Lord_Pepe",
|
||||
"ixcetotis",
|
||||
"btdc00",
|
||||
"Bnuy",
|
||||
"Choccy"
|
||||
],
|
||||
"generation": {
|
||||
"items": {
|
||||
@ -2505,11 +2507,27 @@
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"food": {
|
||||
"weights": {
|
||||
"0": 6,
|
||||
"1": 5,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"drink": {
|
||||
"weights": {
|
||||
"0": 6,
|
||||
"1": 5,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 4,
|
||||
"0": 2,
|
||||
"1": 6,
|
||||
"2": 5,
|
||||
"3": 2,
|
||||
"4": 1
|
||||
@ -2985,9 +3003,9 @@
|
||||
"5ac66d9b5acfc4001633997a": 5,
|
||||
"5ae08f0a5acfc408fb1398a1": 4,
|
||||
"5b0bbe4e5acfc40dc528a72d": 4,
|
||||
"5ba26383d4351e00334c93d9": 5,
|
||||
"5ba26383d4351e00334c93d9": 4,
|
||||
"5bb2475ed4351e00853264e3": 4,
|
||||
"5bd70322209c4d00d7167b8f": 5,
|
||||
"5bd70322209c4d00d7167b8f": 4,
|
||||
"5beed0f50db834001c062b12": 3,
|
||||
"5bf3e03b0db834001d2c4a9c": 5,
|
||||
"5bf3e0490db83400196199af": 5,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -33,6 +33,7 @@
|
||||
"dependencies": {
|
||||
"atomically": "~1.7",
|
||||
"buffer-crc32": "^1.0.0",
|
||||
"closest-match": "~1.3",
|
||||
"date-fns": "~2.30",
|
||||
"date-fns-tz": "~2.0",
|
||||
"i18n": "~0.15",
|
||||
|
@ -4,6 +4,7 @@ import { BotController } from "@spt-aki/controllers/BotController";
|
||||
import { IGenerateBotsRequestData } from "@spt-aki/models/eft/bot/IGenerateBotsRequestData";
|
||||
import { IEmptyRequestData } from "@spt-aki/models/eft/common/IEmptyRequestData";
|
||||
import { IBotBase } from "@spt-aki/models/eft/common/tables/IBotBase";
|
||||
import { Difficulties } from "@spt-aki/models/eft/common/tables/IBotType";
|
||||
import { IGetBodyResponseData } from "@spt-aki/models/eft/httpResponse/IGetBodyResponseData";
|
||||
import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil";
|
||||
|
||||
@ -44,6 +45,15 @@ export class BotCallbacks
|
||||
return this.httpResponse.noBody(this.botController.getBotDifficulty(type, difficulty));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle singleplayer/settings/bot/difficulties
|
||||
* @returns dictionary of every bot and its diffiulty settings
|
||||
*/
|
||||
public getAllBotDifficulties(url: string, info: IEmptyRequestData, sessionID: string): Record<string, Difficulties>
|
||||
{
|
||||
return this.httpResponse.noBody(this.botController.getAllBotDifficulties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/game/bot/generate
|
||||
* @returns IGetBodyResponseData
|
||||
|
@ -65,15 +65,6 @@ export class InraidCallbacks
|
||||
return this.httpResponse.noBody(this.inraidController.getInraidConfig().raidMenuSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle singleplayer/settings/weapon/durability
|
||||
* @returns
|
||||
*/
|
||||
public getWeaponDurability(): string
|
||||
{
|
||||
return this.httpResponse.noBody(this.inraidController.getInraidConfig().save.durability);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle singleplayer/airdrop/config
|
||||
* @returns JSON as string
|
||||
|
@ -83,7 +83,7 @@ export class BotController
|
||||
|
||||
/**
|
||||
* Get bot difficulty settings
|
||||
* adjust PMC settings to ensure they engage the correct bot types
|
||||
* Adjust PMC settings to ensure they engage the correct bot types
|
||||
* @param type what bot the server is requesting settings for
|
||||
* @param diffLevel difficulty level server requested settings for
|
||||
* @returns Difficulty object
|
||||
@ -104,7 +104,7 @@ export class BotController
|
||||
|
||||
// Check value chosen in pre-raid difficulty dropdown
|
||||
// If value is not 'asonline', change requested difficulty to be what was chosen in dropdown
|
||||
const botDifficultyDropDownValue = raidConfig.wavesSettings.botDifficulty.toLowerCase();
|
||||
const botDifficultyDropDownValue = raidConfig?.wavesSettings.botDifficulty.toLowerCase() ?? "asonline";
|
||||
if (botDifficultyDropDownValue !== "asonline")
|
||||
{
|
||||
difficulty = this.botDifficultyHelper.convertBotDifficultyDropdownToBotDifficulty(
|
||||
@ -140,6 +140,31 @@ export class BotController
|
||||
return difficultySettings;
|
||||
}
|
||||
|
||||
public getAllBotDifficulties(): Record<string, any>
|
||||
{
|
||||
const result = {};
|
||||
|
||||
const botDb = this.databaseServer.getTables().bots.types;
|
||||
const botTypes = Object.keys(botDb);
|
||||
for (const botType of botTypes)
|
||||
{
|
||||
const botDetails = botDb[botType];
|
||||
if (!botDetails.difficulty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const botDifficulties = Object.keys(botDetails.difficulty);
|
||||
|
||||
result[botType] = {};
|
||||
for (const difficulty of botDifficulties)
|
||||
{
|
||||
result[botType][difficulty] = this.getBotDifficulty(botType, difficulty);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate bot profiles and store in cache
|
||||
* @param sessionId Session id
|
||||
|
@ -147,7 +147,7 @@ export class BuildController
|
||||
this.removePlayerBuild(request.id, sessionID);
|
||||
}
|
||||
|
||||
protected removePlayerBuild(id: string, sessionID: string): void
|
||||
protected removePlayerBuild(idToRemove: string, sessionID: string): void
|
||||
{
|
||||
const profile = this.saveServer.getProfile(sessionID);
|
||||
const weaponBuilds = profile.userbuilds.weaponBuilds;
|
||||
@ -155,7 +155,7 @@ export class BuildController
|
||||
const magazineBuilds = profile.userbuilds.magazineBuilds;
|
||||
|
||||
// Check for id in weapon array first
|
||||
const matchingWeaponBuild = weaponBuilds.find((x) => x.Id === id);
|
||||
const matchingWeaponBuild = weaponBuilds.find((weaponBuild) => weaponBuild.Id === idToRemove);
|
||||
if (matchingWeaponBuild)
|
||||
{
|
||||
weaponBuilds.splice(weaponBuilds.indexOf(matchingWeaponBuild), 1);
|
||||
@ -164,7 +164,7 @@ export class BuildController
|
||||
}
|
||||
|
||||
// Id not found in weapons, try equipment
|
||||
const matchingEquipmentBuild = equipmentBuilds.find((x) => x.Id === id);
|
||||
const matchingEquipmentBuild = equipmentBuilds.find((equipmentBuild) => equipmentBuild.Id === idToRemove);
|
||||
if (matchingEquipmentBuild)
|
||||
{
|
||||
equipmentBuilds.splice(equipmentBuilds.indexOf(matchingEquipmentBuild), 1);
|
||||
@ -173,7 +173,7 @@ export class BuildController
|
||||
}
|
||||
|
||||
// Id not found in weapons/equipment, try mags
|
||||
const matchingMagazineBuild = magazineBuilds.find((x) => x.Id === id);
|
||||
const matchingMagazineBuild = magazineBuilds.find((magBuild) => magBuild.Id === idToRemove);
|
||||
if (matchingMagazineBuild)
|
||||
{
|
||||
magazineBuilds.splice(magazineBuilds.indexOf(matchingMagazineBuild), 1);
|
||||
@ -182,7 +182,9 @@ export class BuildController
|
||||
}
|
||||
|
||||
// Not found in weapons,equipment or magazines, not good
|
||||
this.logger.error(`Unable to delete preset, cannot find ${id} in weapon, equipment or magazine presets`);
|
||||
this.logger.error(
|
||||
`Unable to delete preset, cannot find ${idToRemove} in weapon, equipment or magazine presets`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,6 +39,7 @@ import { GiftService } from "@spt-aki/services/GiftService";
|
||||
import { ItemBaseClassService } from "@spt-aki/services/ItemBaseClassService";
|
||||
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||
import { OpenZoneService } from "@spt-aki/services/OpenZoneService";
|
||||
import { ProfileActivityService } from "@spt-aki/services/ProfileActivityService";
|
||||
import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
|
||||
import { RaidTimeAdjustmentService } from "@spt-aki/services/RaidTimeAdjustmentService";
|
||||
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
|
||||
@ -78,6 +79,7 @@ export class GameController
|
||||
@inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService,
|
||||
@inject("GiftService") protected giftService: GiftService,
|
||||
@inject("RaidTimeAdjustmentService") protected raidTimeAdjustmentService: RaidTimeAdjustmentService,
|
||||
@inject("ProfileActivityService") protected profileActivityService: ProfileActivityService,
|
||||
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
)
|
||||
@ -109,6 +111,8 @@ export class GameController
|
||||
// Store client start time in app context
|
||||
this.applicationContext.addValue(ContextVariableType.CLIENT_START_TIMESTAMP, startTimeStampMS);
|
||||
|
||||
this.profileActivityService.setActivityTimestamp(sessionID);
|
||||
|
||||
if (this.coreConfig.fixes.fixShotgunDispersion)
|
||||
{
|
||||
this.fixShotgunDispersions();
|
||||
@ -203,12 +207,16 @@ export class GameController
|
||||
this.hideoutHelper.setHideoutImprovementsToCompleted(pmcProfile);
|
||||
this.hideoutHelper.unlockHideoutWallInProfile(pmcProfile);
|
||||
this.profileFixerService.addMissingIdsToBonuses(pmcProfile);
|
||||
this.profileFixerService.fixBitcoinProductionTime(pmcProfile);
|
||||
}
|
||||
|
||||
this.logProfileDetails(fullProfile);
|
||||
|
||||
this.adjustLabsRaiderSpawnRate();
|
||||
|
||||
this.adjustHideoutCraftTimes();
|
||||
this.adjustHideoutBuildTimes();
|
||||
|
||||
this.removePraporTestMessage();
|
||||
|
||||
this.saveActiveModsToProfile(fullProfile);
|
||||
@ -240,6 +248,46 @@ export class GameController
|
||||
}
|
||||
}
|
||||
|
||||
protected adjustHideoutCraftTimes(): void
|
||||
{
|
||||
const craftTimeOverrideSeconds = this.hideoutConfig.overrideCraftTimeSeconds;
|
||||
if (craftTimeOverrideSeconds === -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const craft of this.databaseServer.getTables().hideout.production)
|
||||
{
|
||||
// Only adjust crafts ABOVE the override
|
||||
if (craft.productionTime > craftTimeOverrideSeconds)
|
||||
{
|
||||
craft.productionTime = craftTimeOverrideSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected adjustHideoutBuildTimes(): void
|
||||
{
|
||||
const craftTimeOverrideSeconds = this.hideoutConfig.overrideBuildTimeSeconds;
|
||||
if (craftTimeOverrideSeconds === -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const area of this.databaseServer.getTables().hideout.areas)
|
||||
{
|
||||
for (const stageKey of Object.keys(area.stages))
|
||||
{
|
||||
const stage = area.stages[stageKey];
|
||||
// Only adjust crafts ABOVE the override
|
||||
if (stage.constructionTime > craftTimeOverrideSeconds)
|
||||
{
|
||||
stage.constructionTime = craftTimeOverrideSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected adjustLocationBotValues(): void
|
||||
{
|
||||
const mapsDb = this.databaseServer.getTables().locations;
|
||||
@ -460,6 +508,7 @@ export class GameController
|
||||
*/
|
||||
public getKeepAlive(sessionId: string): IGameKeepAliveResponse
|
||||
{
|
||||
this.profileActivityService.setActivityTimestamp(sessionId);
|
||||
return { msg: "OK", utc_time: new Date().getTime() / 1000 };
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ import { SaveServer } from "@spt-aki/servers/SaveServer";
|
||||
import { FenceService } from "@spt-aki/services/FenceService";
|
||||
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||
import { PlayerService } from "@spt-aki/services/PlayerService";
|
||||
import { ProfileActivityService } from "@spt-aki/services/ProfileActivityService";
|
||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||
import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil";
|
||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||
@ -79,6 +80,7 @@ export class HideoutController
|
||||
@inject("HideoutHelper") protected hideoutHelper: HideoutHelper,
|
||||
@inject("ScavCaseRewardGenerator") protected scavCaseRewardGenerator: ScavCaseRewardGenerator,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ProfileActivityService") protected profileActivityService: ProfileActivityService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||
@inject("FenceService") protected fenceService: FenceService,
|
||||
@ -1323,7 +1325,13 @@ export class HideoutController
|
||||
{
|
||||
for (const sessionID in this.saveServer.getProfiles())
|
||||
{
|
||||
if ("Hideout" in this.saveServer.getProfile(sessionID).characters.pmc)
|
||||
if (
|
||||
"Hideout" in this.saveServer.getProfile(sessionID).characters.pmc
|
||||
&& this.profileActivityService.activeWithinLastMinutes(
|
||||
sessionID,
|
||||
this.hideoutConfig.updateProfileHideoutWhenActiveWithinMinutes,
|
||||
)
|
||||
)
|
||||
{
|
||||
this.hideoutHelper.updatePlayerHideout(sessionID);
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ export class InsuranceController
|
||||
!this.itemHelper.isAttachmentAttached(item)
|
||||
);
|
||||
|
||||
// Process all items that are not attached, attachments. Those are handled separately, by value.
|
||||
// Process all items that are not attached, attachments; those are handled separately, by value.
|
||||
if (hasRegularItems)
|
||||
{
|
||||
this.processRegularItems(insured, toDelete, parentAttachmentsMap);
|
||||
|
@ -172,8 +172,18 @@ export class LauncherController
|
||||
return sessionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle launcher requesting profile be wiped
|
||||
* @param info IRegisterData
|
||||
* @returns Session id
|
||||
*/
|
||||
public wipe(info: IRegisterData): string
|
||||
{
|
||||
if (!this.coreConfig.allowProfileWipe)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionID = this.login(info);
|
||||
|
||||
if (sessionID)
|
||||
|
@ -213,6 +213,7 @@ import { OpenZoneService } from "@spt-aki/services/OpenZoneService";
|
||||
import { PaymentService } from "@spt-aki/services/PaymentService";
|
||||
import { PlayerService } from "@spt-aki/services/PlayerService";
|
||||
import { PmcChatResponseService } from "@spt-aki/services/PmcChatResponseService";
|
||||
import { ProfileActivityService } from "@spt-aki/services/ProfileActivityService";
|
||||
import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
|
||||
import { ProfileSnapshotService } from "@spt-aki/services/ProfileSnapshotService";
|
||||
import { RagfairCategoriesService } from "@spt-aki/services/RagfairCategoriesService";
|
||||
@ -596,7 +597,7 @@ export class Container
|
||||
lifecycle: Lifecycle.Singleton,
|
||||
});
|
||||
// SptCommands
|
||||
depContainer.register<GiveSptCommand>("GiveSptCommand", GiveSptCommand);
|
||||
depContainer.register<GiveSptCommand>("GiveSptCommand", GiveSptCommand, { lifecycle: Lifecycle.Singleton });
|
||||
}
|
||||
|
||||
private static registerLoaders(depContainer: DependencyContainer): void
|
||||
@ -747,6 +748,10 @@ export class Container
|
||||
depContainer.register<GiftService>("GiftService", GiftService);
|
||||
depContainer.register<MailSendService>("MailSendService", MailSendService);
|
||||
depContainer.register<RaidTimeAdjustmentService>("RaidTimeAdjustmentService", RaidTimeAdjustmentService);
|
||||
|
||||
depContainer.register<ProfileActivityService>("ProfileActivityService", ProfileActivityService, {
|
||||
lifecycle: Lifecycle.Singleton,
|
||||
});
|
||||
}
|
||||
|
||||
private static registerServers(depContainer: DependencyContainer): void
|
||||
|
@ -258,13 +258,15 @@ export class BotGenerator
|
||||
* @param botJsonTemplate x.json from database
|
||||
* @param botGenerationDetails
|
||||
* @param botRole role of bot e.g. assault
|
||||
* @param sessionId profile session id
|
||||
* @returns Nickname for bot
|
||||
*/
|
||||
// TODO: Remove sessionId parameter from this function in v3.9.0
|
||||
protected generateBotNickname(
|
||||
botJsonTemplate: IBotType,
|
||||
botGenerationDetails: BotGenerationDetails,
|
||||
botRole: string,
|
||||
sessionId: string,
|
||||
sessionId?: string, // @deprecated as of v3.8.1
|
||||
): string
|
||||
{
|
||||
const isPlayerScav = botGenerationDetails.isPlayerScav;
|
||||
@ -273,9 +275,9 @@ export class BotGenerator
|
||||
this.randomUtil.getArrayValue(botJsonTemplate.lastName) || ""
|
||||
}`;
|
||||
name = name.trim();
|
||||
const playerProfile = this.profileHelper.getPmcProfile(sessionId);
|
||||
|
||||
// Simulate bot looking like a Player scav with the pmc name in brackets
|
||||
// Simulate bot looking like a player scav with the PMC name in brackets.
|
||||
// E.g. "ScavName (PMCName)"
|
||||
if (botRole === "assault" && this.randomUtil.getChance100(this.botConfig.chanceAssaultScavHasPlayerScavName))
|
||||
{
|
||||
if (isPlayerScav)
|
||||
@ -300,7 +302,7 @@ export class BotGenerator
|
||||
if (botGenerationDetails.isPmc && botGenerationDetails.allPmcsHaveSameNameAsPlayer)
|
||||
{
|
||||
const prefix = this.localisationService.getRandomTextThatMatchesPartialKey("pmc-name_prefix_");
|
||||
name = `${prefix} ${botGenerationDetails.playerName}`;
|
||||
name = `${prefix} ${name}`;
|
||||
}
|
||||
|
||||
return name;
|
||||
|
@ -98,6 +98,10 @@ export class BotLootGenerator
|
||||
);
|
||||
const healingItemCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.healing.weights));
|
||||
const drugItemCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.drugs.weights));
|
||||
|
||||
const foodItemCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.food.weights));
|
||||
const drinkItemCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.drink.weights));
|
||||
|
||||
const stimItemCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.stims.weights));
|
||||
const grenadeCount = Number(this.weightedRandomHelper.getWeightedValue<number>(itemCounts.grenades.weights));
|
||||
|
||||
@ -145,6 +149,30 @@ export class BotLootGenerator
|
||||
isPmc,
|
||||
);
|
||||
|
||||
// Food
|
||||
this.addLootFromPool(
|
||||
this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.FOOD_ITEMS, botJsonTemplate),
|
||||
containersBotHasAvailable,
|
||||
foodItemCount,
|
||||
botInventory,
|
||||
botRole,
|
||||
null,
|
||||
0,
|
||||
isPmc,
|
||||
);
|
||||
|
||||
// Drink
|
||||
this.addLootFromPool(
|
||||
this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.DRINK_ITEMS, botJsonTemplate),
|
||||
containersBotHasAvailable,
|
||||
drinkItemCount,
|
||||
botInventory,
|
||||
botRole,
|
||||
null,
|
||||
0,
|
||||
isPmc,
|
||||
);
|
||||
|
||||
// Stims
|
||||
this.addLootFromPool(
|
||||
this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.STIM_ITEMS, botJsonTemplate),
|
||||
@ -282,19 +310,6 @@ export class BotLootGenerator
|
||||
true,
|
||||
);
|
||||
|
||||
// eTG regen stim
|
||||
this.addLootFromPool(
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
{ "5c0e534186f7747fa1419867": 1 },
|
||||
[EquipmentSlots.SECURED_CONTAINER],
|
||||
2,
|
||||
botInventory,
|
||||
botRole,
|
||||
null,
|
||||
0,
|
||||
true,
|
||||
);
|
||||
|
||||
// AFAK
|
||||
this.addLootFromPool(
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
|
@ -74,7 +74,7 @@ export class FenceBaseAssortGenerator
|
||||
}
|
||||
}
|
||||
|
||||
// Only allow rigs with no slots (carrier rigs)
|
||||
// Only allow rigs with no slots (carrier rigs)
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.VEST) && rootItemDb._props.Slots.length > 0)
|
||||
{
|
||||
continue;
|
||||
@ -95,6 +95,15 @@ export class FenceBaseAssortGenerator
|
||||
upd: { StackObjectsCount: 9999999 },
|
||||
}];
|
||||
|
||||
// Ensure ammo is not above penetration limit value
|
||||
if (this.itemHelper.isOfBaseclasses(rootItemDb._id, [BaseClasses.AMMO_BOX, BaseClasses.AMMO]))
|
||||
{
|
||||
if (this.isAmmoAbovePenetrationLimit(rootItemDb))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
this.itemHelper.addCartridgesToAmmoBox(itemWithChildrenToAdd, rootItemDb);
|
||||
@ -175,6 +184,53 @@ export class FenceBaseAssortGenerator
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check ammo in boxes + loose ammos has a penetration value above the configured value in trader.json / ammoMaxPenLimit
|
||||
* @param rootItemDb Ammo box or ammo item from items.db
|
||||
* @returns True if penetration value is above limit set in config
|
||||
*/
|
||||
protected isAmmoAbovePenetrationLimit(rootItemDb: ITemplateItem): boolean
|
||||
{
|
||||
const ammoPenetrationPower = this.getAmmoPenetrationPower(rootItemDb);
|
||||
if (ammoPenetrationPower === null)
|
||||
{
|
||||
this.logger.warning(`Ammo: ${rootItemDb._id} has no penetration value, skipping`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return ammoPenetrationPower > this.traderConfig.fence.ammoMaxPenLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the penetration power value of an ammo, works with ammo boxes and raw ammos
|
||||
* @param rootItemDb Ammo box or ammo item from items.db
|
||||
* @returns Penetration power of passed in item, null if it doesnt have a power
|
||||
*/
|
||||
protected getAmmoPenetrationPower(rootItemDb: ITemplateItem): number
|
||||
{
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
const ammoTplInBox = rootItemDb._props.StackSlots[0]._props.filters[0].Filter[0];
|
||||
const ammoItemDb = this.itemHelper.getItem(ammoTplInBox);
|
||||
if (!ammoItemDb[0])
|
||||
{
|
||||
this.logger.warning(`Ammo: ${ammoTplInBox} not an item, skipping`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return ammoItemDb[1]._props.PenetrationPower;
|
||||
}
|
||||
|
||||
// Plain old ammo, get its pen property
|
||||
if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.AMMO))
|
||||
{
|
||||
return rootItemDb._props.PenetrationPower;
|
||||
}
|
||||
|
||||
// Not an ammobox or ammo
|
||||
return null;
|
||||
}
|
||||
|
||||
protected getItemPrice(itemTpl: string, items: Item[]): number
|
||||
{
|
||||
return this.itemHelper.isOfBaseclass(itemTpl, BaseClasses.AMMO_BOX)
|
||||
|
@ -871,7 +871,7 @@ export class LocationGenerator
|
||||
// Create array with just magazine
|
||||
const magazineItem: Item[] = [{ _id: this.objectId.generate(), _tpl: chosenTpl }];
|
||||
|
||||
if (this.randomUtil.getChance100(this.locationConfig.magazineLootHasAmmoChancePercent))
|
||||
if (this.randomUtil.getChance100(this.locationConfig.staticMagazineLootHasAmmoChancePercent))
|
||||
{
|
||||
// Add randomised amount of cartridges
|
||||
this.itemHelper.fillMagazineWithRandomCartridge(
|
||||
|
@ -99,12 +99,15 @@ export class LootGenerator
|
||||
&& options.itemTypeWhitelist.includes(x[1]._parent)
|
||||
);
|
||||
|
||||
const randomisedItemCount = this.randomUtil.getInt(options.itemCount.min, options.itemCount.max);
|
||||
for (let index = 0; index < randomisedItemCount; index++)
|
||||
if (items.length > 0)
|
||||
{
|
||||
if (!this.findAndAddRandomItemToLoot(items, itemTypeCounts, options, result))
|
||||
const randomisedItemCount = this.randomUtil.getInt(options.itemCount.min, options.itemCount.max);
|
||||
for (let index = 0; index < randomisedItemCount; index++)
|
||||
{
|
||||
index--;
|
||||
if (!this.findAndAddRandomItemToLoot(items, itemTypeCounts, options, result))
|
||||
{
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,13 +125,21 @@ export class LootGenerator
|
||||
this.itemHelper.isOfBaseclass(preset._encyclopedia, BaseClasses.WEAPON)
|
||||
);
|
||||
|
||||
for (let index = 0; index < randomisedWeaponPresetCount; index++)
|
||||
if (weaponDefaultPresets.length > 0)
|
||||
{
|
||||
if (
|
||||
!this.findAndAddRandomPresetToLoot(weaponDefaultPresets, itemTypeCounts, itemBlacklistArray, result)
|
||||
)
|
||||
for (let index = 0; index < randomisedWeaponPresetCount; index++)
|
||||
{
|
||||
index--;
|
||||
if (
|
||||
!this.findAndAddRandomPresetToLoot(
|
||||
weaponDefaultPresets,
|
||||
itemTypeCounts,
|
||||
itemBlacklistArray,
|
||||
result,
|
||||
)
|
||||
)
|
||||
{
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -146,18 +157,22 @@ export class LootGenerator
|
||||
const levelFilteredArmorPresets = armorDefaultPresets.filter((armor) =>
|
||||
this.armorIsDesiredProtectionLevel(armor, options)
|
||||
);
|
||||
for (let index = 0; index < randomisedArmorPresetCount; index++)
|
||||
|
||||
if (levelFilteredArmorPresets.length > 0)
|
||||
{
|
||||
if (
|
||||
!this.findAndAddRandomPresetToLoot(
|
||||
levelFilteredArmorPresets,
|
||||
itemTypeCounts,
|
||||
itemBlacklistArray,
|
||||
result,
|
||||
)
|
||||
)
|
||||
for (let index = 0; index < randomisedArmorPresetCount; index++)
|
||||
{
|
||||
index--;
|
||||
if (
|
||||
!this.findAndAddRandomPresetToLoot(
|
||||
levelFilteredArmorPresets,
|
||||
itemTypeCounts,
|
||||
itemBlacklistArray,
|
||||
result,
|
||||
)
|
||||
)
|
||||
{
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -307,7 +322,7 @@ export class LootGenerator
|
||||
const randomPreset = this.randomUtil.getArrayValue(globalDefaultPresets);
|
||||
if (!randomPreset?._encyclopedia)
|
||||
{
|
||||
this.logger.debug(`Airdrop - preset with id: ${randomPreset._id} lacks encyclopedia property, skipping`);
|
||||
this.logger.debug(`Airdrop - preset with id: ${randomPreset?._id} lacks encyclopedia property, skipping`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ export class PMCLootGenerator
|
||||
|
||||
for (const itemToAdd of itemsToAdd)
|
||||
{
|
||||
// If pmc has override, use that. Otherwise use flea price
|
||||
// If pmc has price override, use that. Otherwise use flea price
|
||||
if (pmcPriceOverrides[itemToAdd._id])
|
||||
{
|
||||
this.backpackLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id];
|
||||
|
@ -688,19 +688,19 @@ export class BotGeneratorHelper
|
||||
const itemDetails = this.itemHelper.getItem(itemTpl)[1];
|
||||
|
||||
// if item to add is found in exclude filter, not allowed
|
||||
if (excludedFilter.includes(itemDetails._parent))
|
||||
if (excludedFilter?.includes(itemDetails._parent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If Filter array only contains 1 filter and its for basetype 'item', allow it
|
||||
if (filter.length === 1 && filter.includes(BaseClasses.ITEM))
|
||||
if (filter?.length === 1 && filter.includes(BaseClasses.ITEM))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If allowed filter has something in it + filter doesnt have basetype 'item', not allowed
|
||||
if (filter.length > 0 && !filter.includes(itemDetails._parent))
|
||||
if (filter?.length > 0 && !filter.includes(itemDetails._parent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
75
project/src/helpers/Dialogue/AbstractDialogueChatBot.ts
Normal file
75
project/src/helpers/Dialogue/AbstractDialogueChatBot.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { IChatCommand, ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/IChatCommand";
|
||||
import { IDialogueChatBot } from "@spt-aki/helpers/Dialogue/IDialogueChatBot";
|
||||
import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
|
||||
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { MailSendService } from "@spt-aki/services/MailSendService";
|
||||
|
||||
export abstract class AbstractDialogueChatBot implements IDialogueChatBot
|
||||
{
|
||||
public constructor(
|
||||
protected logger: ILogger,
|
||||
protected mailSendService: MailSendService,
|
||||
protected chatCommands: IChatCommand[] | ICommandoCommand[],
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v3.7.6. Use registerChatCommand.
|
||||
*/
|
||||
// TODO: v3.9.0 - Remove registerCommandoCommand method.
|
||||
public registerCommandoCommand(chatCommand: IChatCommand | ICommandoCommand): void
|
||||
{
|
||||
this.registerChatCommand(chatCommand);
|
||||
}
|
||||
|
||||
public registerChatCommand(chatCommand: IChatCommand | ICommandoCommand): void
|
||||
{
|
||||
if (this.chatCommands.some((cc) => cc.getCommandPrefix() === chatCommand.getCommandPrefix()))
|
||||
{
|
||||
throw new Error(
|
||||
`The command "${chatCommand.getCommandPrefix()}" attempting to be registered already exists.`,
|
||||
);
|
||||
}
|
||||
this.chatCommands.push(chatCommand);
|
||||
}
|
||||
|
||||
public abstract getChatBot(): IUserDialogInfo;
|
||||
|
||||
protected abstract getUnrecognizedCommandMessage(): string;
|
||||
|
||||
public handleMessage(sessionId: string, request: ISendMessageRequest): string
|
||||
{
|
||||
if ((request.text ?? "").length === 0)
|
||||
{
|
||||
this.logger.error("Command came in as empty text! Invalid data!");
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
const splitCommand = request.text.split(" ");
|
||||
|
||||
const commandos = this.chatCommands.filter((c) => c.getCommandPrefix() === splitCommand[0]);
|
||||
if (commandos[0]?.getCommands().has(splitCommand[1]))
|
||||
{
|
||||
return commandos[0].handle(splitCommand[1], this.getChatBot(), sessionId, request);
|
||||
}
|
||||
|
||||
if (splitCommand[0].toLowerCase() === "help")
|
||||
{
|
||||
const helpMessage = this.chatCommands.map((c) =>
|
||||
`Available commands:\n\n${c.getCommandPrefix()}:\n\n${
|
||||
Array.from(c.getCommands()).map((command) => c.getCommandHelp(command)).join("\n")
|
||||
}`
|
||||
).join("\n");
|
||||
this.mailSendService.sendUserMessageToPlayer(sessionId, this.getChatBot(), helpMessage);
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
this.getChatBot(),
|
||||
this.getUnrecognizedCommandMessage(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
|
||||
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
|
||||
|
||||
export interface ICommandoCommand
|
||||
/**
|
||||
* @deprecated As of v3.7.6. Use IChatCommand. Will be removed in v3.9.0.
|
||||
*/
|
||||
// TODO: v3.9.0 - Remove ICommandoCommand.
|
||||
export type ICommandoCommand = IChatCommand;
|
||||
export interface IChatCommand
|
||||
{
|
||||
getCommandPrefix(): string;
|
||||
getCommandHelp(command: string): string;
|
@ -1,4 +1,4 @@
|
||||
import { ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/ICommandoCommand";
|
||||
import { IChatCommand } from "@spt-aki/helpers/Dialogue/Commando/IChatCommand";
|
||||
import { ISptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ISptCommand";
|
||||
import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
|
||||
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
|
||||
@ -8,7 +8,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
import { inject, injectAll, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class SptCommandoCommands implements ICommandoCommand
|
||||
export class SptCommandoCommands implements IChatCommand
|
||||
{
|
||||
constructor(
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@ -31,7 +31,7 @@ export class SptCommandoCommands implements ICommandoCommand
|
||||
{
|
||||
if (this.sptCommands.some((c) => c.getCommand() === command.getCommand()))
|
||||
{
|
||||
throw new Error(`The command ${command.getCommand()} being registered for SPT Commands already exists!`);
|
||||
throw new Error(`The command "${command.getCommand()}" attempting to be registered already exists.`);
|
||||
}
|
||||
this.sptCommands.push(command);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ISptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ISptCommand";
|
||||
import { SavedCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/SavedCommand";
|
||||
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
||||
import { PresetHelper } from "@spt-aki/helpers/PresetHelper";
|
||||
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
@ -6,14 +7,30 @@ import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequ
|
||||
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
|
||||
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import { LocaleService } from "@spt-aki/services/LocaleService";
|
||||
import { MailSendService } from "@spt-aki/services/MailSendService";
|
||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||
import { closestMatch, distance } from "closest-match";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class GiveSptCommand implements ISptCommand
|
||||
{
|
||||
/**
|
||||
* Regex to account for all these cases:
|
||||
* spt give "item name" 5
|
||||
* spt give templateId 5
|
||||
* spt give en "item name in english" 5
|
||||
* spt give es "nombre en español" 5
|
||||
* spt give 5 <== this is the reply when the algo isn't sure about an item
|
||||
*/
|
||||
private static commandRegex = /^spt give (((([a-z]{2,5}) )?"(.+)"|\w+) )?([0-9]+)$/;
|
||||
private static maxAllowedDistance = 1.5;
|
||||
|
||||
protected savedCommand: SavedCommand;
|
||||
|
||||
public constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@ -21,6 +38,8 @@ export class GiveSptCommand implements ISptCommand
|
||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||
@inject("PresetHelper") protected presetHelper: PresetHelper,
|
||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||
@inject("LocaleService") protected localeService: LocaleService,
|
||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||
)
|
||||
{
|
||||
}
|
||||
@ -32,49 +51,135 @@ export class GiveSptCommand implements ISptCommand
|
||||
|
||||
public getCommandHelp(): string
|
||||
{
|
||||
return "Usage: spt give tplId quantity";
|
||||
return "spt give\n========\nSends items to the player through the message system.\n\n\tspt give [template ID] [quantity]\n\t\tEx: spt give 544fb25a4bdc2dfb738b4567 2\n\n\tspt give [\"item name\"] [quantity]\n\t\tEx: spt give \"pack of sugar\" 10\n\n\tspt give [locale] [\"item name\"] [quantity]\n\t\tEx: spt give fr \"figurine de chat\" 3";
|
||||
}
|
||||
|
||||
public performAction(commandHandler: IUserDialogInfo, sessionId: string, request: ISendMessageRequest): string
|
||||
{
|
||||
const giveCommand = request.text.split(" ");
|
||||
if (giveCommand[1] !== "give")
|
||||
{
|
||||
this.logger.error("Invalid action received for give command!");
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
if (!giveCommand[2])
|
||||
if (!GiveSptCommand.commandRegex.test(request.text))
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid use of give command! Template ID is missing. Use \"Help\" for more info",
|
||||
"Invalid use of give command. Use \"help\" for more information.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
const tplId = giveCommand[2];
|
||||
|
||||
if (!giveCommand[3])
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid use of give command! Quantity is missing. Use \"Help\" for more info",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
const quantity = giveCommand[3];
|
||||
const result = GiveSptCommand.commandRegex.exec(request.text);
|
||||
|
||||
if (Number.isNaN(+quantity))
|
||||
let item: string;
|
||||
let quantity: number;
|
||||
let isItemName: boolean;
|
||||
let locale: string;
|
||||
|
||||
// This is a reply to a give request previously made pending a reply
|
||||
if (result[1] === undefined)
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid use of give command! Quantity is not a valid integer. Use \"Help\" for more info",
|
||||
);
|
||||
return request.dialogId;
|
||||
if (this.savedCommand === undefined)
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid use of give command. Use \"help\" for more information.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
if (+result[6] > this.savedCommand.potentialItemNames.length)
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid selection. Outside of bounds! Use \"help\" for more information.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
item = this.savedCommand.potentialItemNames[+result[6] - 1];
|
||||
quantity = this.savedCommand.quantity;
|
||||
locale = this.savedCommand.locale;
|
||||
isItemName = true;
|
||||
this.savedCommand = undefined;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A new give request was entered, we need to ignore the old saved command
|
||||
this.savedCommand = undefined;
|
||||
isItemName = result[5] !== undefined;
|
||||
item = result[5] ? result[5] : result[2];
|
||||
quantity = +result[6];
|
||||
|
||||
if (isItemName)
|
||||
{
|
||||
locale = result[4] ? result[4] : this.localeService.getDesiredGameLocale();
|
||||
if (!this.localeService.getServerSupportedLocales().includes(locale))
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
`Unknown locale "${locale}". Use \"help\" for more information.`,
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
const localizedGlobal = this.databaseServer.getTables().locales.global[locale];
|
||||
|
||||
const closestItemsMatchedByName = closestMatch(
|
||||
item.toLowerCase(),
|
||||
this.itemHelper.getItems().filter((i) => i._type !== "Node").map((i) =>
|
||||
localizedGlobal[`${i?._id} Name`]?.toLowerCase()
|
||||
).filter((i) => i !== undefined),
|
||||
true,
|
||||
) as string[];
|
||||
|
||||
if (closestItemsMatchedByName === undefined || closestItemsMatchedByName.length === 0)
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"That item could not be found. Please refine your request and try again.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
if (closestItemsMatchedByName.length > 1)
|
||||
{
|
||||
let i = 1;
|
||||
const slicedItems = closestItemsMatchedByName.slice(0, 10);
|
||||
// max 10 item names and map them
|
||||
const itemList = slicedItems.map((itemName) => `${i++}. ${itemName}`).join("\n");
|
||||
this.savedCommand = new SavedCommand(quantity, slicedItems, locale);
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
`Could not find exact match. Closest matches are:\n\n${itemList}\n\nUse "spt give [number]" to select one.`,
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
const dist = distance(item, closestItemsMatchedByName[0]);
|
||||
if (dist > GiveSptCommand.maxAllowedDistance)
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
`Found a possible match for "${item}" but uncertain. Match: "${
|
||||
closestItemsMatchedByName[0]
|
||||
}". Please refine your request and try again.`,
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
// Only one available so we get that entry and use it
|
||||
item = closestItemsMatchedByName[0];
|
||||
}
|
||||
}
|
||||
|
||||
// If item is an item name, we need to search using that item name and the locale which one we want otherwise
|
||||
// item is just the tplId.
|
||||
const tplId = isItemName
|
||||
? this.itemHelper.getItems().find((i) =>
|
||||
this.databaseServer.getTables().locales.global[locale][`${i?._id} Name`]?.toLowerCase() === item
|
||||
)._id
|
||||
: item;
|
||||
|
||||
const checkedItem = this.itemHelper.getItem(tplId);
|
||||
if (!checkedItem[0])
|
||||
@ -82,21 +187,25 @@ export class GiveSptCommand implements ISptCommand
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid template ID requested for give command. The item doesn't exist in the DB.",
|
||||
"That item could not be found. Please refine your request and try again.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
const itemsToSend: Item[] = [];
|
||||
const preset = this.presetHelper.getDefaultPreset(checkedItem[1]._id);
|
||||
if (preset)
|
||||
if (this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.WEAPON))
|
||||
{
|
||||
for (let i = 0; i < +quantity; i++)
|
||||
const preset = this.presetHelper.getDefaultPreset(checkedItem[1]._id);
|
||||
if (!preset)
|
||||
{
|
||||
// Make sure IDs are unique before adding to array - prevent collisions
|
||||
const presetToSend = this.itemHelper.replaceIDs(preset._items);
|
||||
itemsToSend.push(...presetToSend);
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"That weapon template ID could not be found. Please refine your request and try again.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
itemsToSend.push(...this.jsonUtil.clone(preset._items));
|
||||
}
|
||||
else if (this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
@ -115,13 +224,25 @@ export class GiveSptCommand implements ISptCommand
|
||||
_tpl: checkedItem[1]._id,
|
||||
upd: { StackObjectsCount: +quantity, SpawnedInSession: true },
|
||||
};
|
||||
itemsToSend.push(...this.itemHelper.splitStack(item));
|
||||
try
|
||||
{
|
||||
itemsToSend.push(...this.itemHelper.splitStack(item));
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Too many items requested. Please lower the amount and try again.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
}
|
||||
|
||||
// Flag the items as FiR
|
||||
this.itemHelper.setFoundInRaid(itemsToSend);
|
||||
|
||||
this.mailSendService.sendSystemMessageToPlayer(sessionId, "Give command!", itemsToSend);
|
||||
this.mailSendService.sendSystemMessageToPlayer(sessionId, "SPT GIVE", itemsToSend);
|
||||
return request.dialogId;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
export class SavedCommand
|
||||
{
|
||||
public constructor(public quantity: number, public potentialItemNames: string[], public locale: string)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,33 +1,22 @@
|
||||
import { inject, injectAll, injectable } from "tsyringe";
|
||||
|
||||
import { ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/ICommandoCommand";
|
||||
import { IDialogueChatBot } from "@spt-aki/helpers/Dialogue/IDialogueChatBot";
|
||||
import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
|
||||
import { AbstractDialogueChatBot } from "@spt-aki/helpers/Dialogue/AbstractDialogueChatBot";
|
||||
import { IChatCommand } from "@spt-aki/helpers/Dialogue/Commando/IChatCommand";
|
||||
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
|
||||
import { MemberCategory } from "@spt-aki/models/enums/MemberCategory";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { MailSendService } from "@spt-aki/services/MailSendService";
|
||||
|
||||
@injectable()
|
||||
export class CommandoDialogueChatBot implements IDialogueChatBot
|
||||
export class CommandoDialogueChatBot extends AbstractDialogueChatBot
|
||||
{
|
||||
public constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||
@injectAll("CommandoCommand") protected commandoCommands: ICommandoCommand[],
|
||||
@inject("WinstonLogger") logger: ILogger,
|
||||
@inject("MailSendService") mailSendService: MailSendService,
|
||||
@injectAll("CommandoCommand") chatCommands: IChatCommand[],
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public registerCommandoCommand(commandoCommand: ICommandoCommand): void
|
||||
{
|
||||
if (this.commandoCommands.some((cc) => cc.getCommandPrefix() === commandoCommand.getCommandPrefix()))
|
||||
{
|
||||
throw new Error(
|
||||
`The commando command ${commandoCommand.getCommandPrefix()} being registered already exists!`,
|
||||
);
|
||||
}
|
||||
this.commandoCommands.push(commandoCommand);
|
||||
super(logger, mailSendService, chatCommands);
|
||||
}
|
||||
|
||||
public getChatBot(): IUserDialogInfo
|
||||
@ -39,37 +28,8 @@ export class CommandoDialogueChatBot implements IDialogueChatBot
|
||||
};
|
||||
}
|
||||
|
||||
public handleMessage(sessionId: string, request: ISendMessageRequest): string
|
||||
protected getUnrecognizedCommandMessage(): string
|
||||
{
|
||||
if ((request.text ?? "").length === 0)
|
||||
{
|
||||
this.logger.error("Commando command came in as empty text! Invalid data!");
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
const splitCommand = request.text.split(" ");
|
||||
|
||||
const commandos = this.commandoCommands.filter((c) => c.getCommandPrefix() === splitCommand[0]);
|
||||
if (commandos[0]?.getCommands().has(splitCommand[1]))
|
||||
{
|
||||
return commandos[0].handle(splitCommand[1], this.getChatBot(), sessionId, request);
|
||||
}
|
||||
|
||||
if (splitCommand[0].toLowerCase() === "help")
|
||||
{
|
||||
const helpMessage = this.commandoCommands.map((c) =>
|
||||
`Help for ${c.getCommandPrefix()}:\n${
|
||||
Array.from(c.getCommands()).map((command) => c.getCommandHelp(command)).join("\n")
|
||||
}`
|
||||
).join("\n");
|
||||
this.mailSendService.sendUserMessageToPlayer(sessionId, this.getChatBot(), helpMessage);
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
this.getChatBot(),
|
||||
`Im sorry soldier, I dont recognize the command you are trying to use! Type "help" to see available commands.`,
|
||||
);
|
||||
return `I'm sorry soldier, I don't recognize the command you are trying to use! Type "help" to see available commands.`;
|
||||
}
|
||||
}
|
||||
|
@ -595,7 +595,7 @@ export class HideoutHelper
|
||||
* @param applyHideoutManagementBonus should the hideout mgmt bonus be appled to the calculation
|
||||
* @returns Items craft time with bonuses subtracted
|
||||
*/
|
||||
protected getAdjustedCraftTimeWithSkills(
|
||||
public getAdjustedCraftTimeWithSkills(
|
||||
pmcData: IPmcData,
|
||||
recipeId: string,
|
||||
applyHideoutManagementBonus = false,
|
||||
@ -613,13 +613,19 @@ export class HideoutHelper
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Seconds to deduct from crafts total time
|
||||
let timeReductionSeconds = this.getSkillProductionTimeReduction(
|
||||
pmcData,
|
||||
recipe.productionTime,
|
||||
SkillTypes.CRAFTING,
|
||||
globalSkillsDb.Crafting.ProductionTimeReductionPerLevel,
|
||||
);
|
||||
let timeReductionSeconds = 0;
|
||||
|
||||
// Bitcoin farm is excluded from crafting skill cooldown reduction
|
||||
if (recipeId !== HideoutHelper.bitcoinFarm)
|
||||
{
|
||||
// Seconds to deduct from crafts total time
|
||||
timeReductionSeconds += this.getSkillProductionTimeReduction(
|
||||
pmcData,
|
||||
recipe.productionTime,
|
||||
SkillTypes.CRAFTING,
|
||||
globalSkillsDb.Crafting.ProductionTimeReductionPerLevel,
|
||||
);
|
||||
}
|
||||
|
||||
// Some crafts take into account hideout management, e.g. fuel, water/air filters
|
||||
if (applyHideoutManagementBonus)
|
||||
|
@ -32,12 +32,12 @@ export class HttpServerHelper
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine ip and port into url
|
||||
* Combine ip and port into address
|
||||
* @returns url
|
||||
*/
|
||||
public buildUrl(): string
|
||||
{
|
||||
return `${this.httpConfig.ip}:${this.httpConfig.port}`;
|
||||
return `${this.httpConfig.backendIp}:${this.httpConfig.backendPort}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -429,19 +429,12 @@ export class ItemHelper
|
||||
if (repairable.Durability > repairable.MaxDurability)
|
||||
{
|
||||
this.logger.warning(
|
||||
`Max durability: ${repairable.MaxDurability} for item id: ${item._id} was below Durability: ${repairable.Durability}, adjusting values to match`,
|
||||
`Max durability: ${repairable.MaxDurability} for item id: ${item._id} was below durability: ${repairable.Durability}, adjusting values to match`,
|
||||
);
|
||||
repairable.MaxDurability = repairable.Durability;
|
||||
}
|
||||
|
||||
// Armor
|
||||
if (itemDetails._props.armorClass)
|
||||
{
|
||||
return repairable.MaxDurability / itemDetails._props.MaxDurability;
|
||||
}
|
||||
|
||||
// Weapon
|
||||
// Get max dura from props, if it isnt there use repairable max dura value
|
||||
// Attempt to get the max durability from _props. If not available, use Repairable max durability value instead.
|
||||
const maxDurability = (itemDetails._props.MaxDurability)
|
||||
? itemDetails._props.MaxDurability
|
||||
: repairable.MaxDurability;
|
||||
|
@ -85,6 +85,17 @@ export class PresetHelper
|
||||
return id in this.databaseServer.getTables().globals.ItemPresets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the preset is of the given base class.
|
||||
* @param id The id of the preset
|
||||
* @param baseClass The BaseClasses enum to check against
|
||||
* @returns True if the preset is of the given base class, false otherwise
|
||||
*/
|
||||
public isPresetBaseClass(id: string, baseClass: BaseClasses): boolean
|
||||
{
|
||||
return this.isPreset(id) && this.itemHelper.isOfBaseclass(this.getPreset(id)._encyclopedia, baseClass);
|
||||
}
|
||||
|
||||
public hasPreset(templateId: string): boolean
|
||||
{
|
||||
return templateId in this.lookup;
|
||||
|
@ -731,16 +731,20 @@ export class QuestHelper
|
||||
repeatableType.activeQuests
|
||||
).find((activeQuest) => activeQuest._id === failRequest.qid);
|
||||
|
||||
if (matchingRepeatableQuest || quest)
|
||||
// Quest found and no repeatable found
|
||||
if (quest && !matchingRepeatableQuest)
|
||||
{
|
||||
this.mailSendService.sendLocalisedNpcMessageToPlayer(
|
||||
sessionID,
|
||||
this.traderHelper.getTraderById(quest?.traderId ?? matchingRepeatableQuest?.traderId), // Can be null when repeatable quest has been moved to inactiveQuests
|
||||
MessageType.QUEST_FAIL,
|
||||
quest.failMessageText,
|
||||
questRewards,
|
||||
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
|
||||
);
|
||||
if (quest.failMessageText.trim().length > 0)
|
||||
{
|
||||
this.mailSendService.sendLocalisedNpcMessageToPlayer(
|
||||
sessionID,
|
||||
this.traderHelper.getTraderById(quest?.traderId ?? matchingRepeatableQuest?.traderId), // Can be null when repeatable quest has been moved to inactiveQuests
|
||||
MessageType.QUEST_FAIL,
|
||||
quest.failMessageText,
|
||||
questRewards,
|
||||
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
output.profileChanges[sessionID].quests.push(...this.failedUnlocked(failRequest.qid, sessionID));
|
||||
|
@ -158,6 +158,14 @@ export class TradeHelper
|
||||
);
|
||||
}
|
||||
|
||||
// Check if trader has enough stock
|
||||
if (itemPurchased.upd.StackObjectsCount < buyCount)
|
||||
{
|
||||
throw new Error(
|
||||
`Unable to purchase ${buyCount} items, this would exceed the remaining stock left ${itemPurchased.upd.StackObjectsCount} from the traders assort: ${buyRequestData.tid} this refresh`,
|
||||
);
|
||||
}
|
||||
|
||||
// Decrement trader item count
|
||||
itemPurchased.upd.StackObjectsCount -= buyCount;
|
||||
|
||||
|
@ -130,6 +130,8 @@ export interface GenerationWeightingItems
|
||||
grenades: GenerationData;
|
||||
healing: GenerationData;
|
||||
drugs: GenerationData;
|
||||
food: GenerationData;
|
||||
drink: GenerationData;
|
||||
stims: GenerationData;
|
||||
backpackLoot: GenerationData;
|
||||
pocketLoot: GenerationData;
|
||||
|
@ -169,6 +169,7 @@ export interface IQuestReward
|
||||
target?: string;
|
||||
items?: Item[];
|
||||
loyaltyLevel?: number;
|
||||
/** Hideout area id */
|
||||
traderId?: string;
|
||||
unknown?: boolean;
|
||||
findInRaid?: boolean;
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
||||
|
||||
export interface IBotLootCache
|
||||
{
|
||||
backpackLoot: Record<string, number>;
|
||||
@ -11,6 +9,8 @@ export interface IBotLootCache
|
||||
specialItems: Record<string, number>;
|
||||
healingItems: Record<string, number>;
|
||||
drugItems: Record<string, number>;
|
||||
foodItems: Record<string, number>;
|
||||
drinkItems: Record<string, number>;
|
||||
stimItems: Record<string, number>;
|
||||
grenadeItems: Record<string, number>;
|
||||
}
|
||||
@ -27,4 +27,6 @@ export enum LootCacheType
|
||||
DRUG_ITEMS = "DrugItems",
|
||||
STIM_ITEMS = "StimItems",
|
||||
GRENADE_ITEMS = "GrenadeItems",
|
||||
FOOD_ITEMS = "FoodItems",
|
||||
DRINK_ITEMS = "DrinkItems",
|
||||
}
|
||||
|
@ -186,12 +186,9 @@ export interface IAdjustmentDetails
|
||||
edit: Record<string, Record<string, number>>;
|
||||
}
|
||||
|
||||
export interface IArmorPlateWeights
|
||||
export interface IArmorPlateWeights extends Record<string, any>
|
||||
{
|
||||
levelRange: MinMax;
|
||||
frontPlateWeights: Record<string, number>;
|
||||
backPlateWeights: Record<string, number>;
|
||||
sidePlateWeights: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface IRandomisedResourceDetails
|
||||
|
@ -9,6 +9,7 @@ export interface ICoreConfig extends IBaseConfig
|
||||
serverName: string;
|
||||
profileSaveIntervalSeconds: number;
|
||||
sptFriendNickname: string;
|
||||
allowProfileWipe: boolean;
|
||||
bsgLogging: IBsgLogging;
|
||||
release: IRelease;
|
||||
fixes: IGameFixes;
|
||||
|
@ -9,4 +9,8 @@ export interface IHideoutConfig extends IBaseConfig
|
||||
runIntervalValues: IRunIntervalValues;
|
||||
hoursForSkillCrafting: number;
|
||||
expCraftAmount: number;
|
||||
overrideCraftTimeSeconds: number;
|
||||
overrideBuildTimeSeconds: number;
|
||||
/** Only process a profiles hideout crafts when it has been active in the last x minutes */
|
||||
updateProfileHideoutWhenActiveWithinMinutes: number;
|
||||
}
|
||||
|
@ -2,10 +2,14 @@ import { IBaseConfig } from "@spt-aki/models/spt/config/IBaseConfig";
|
||||
|
||||
export interface IHttpConfig extends IBaseConfig
|
||||
{
|
||||
webSocketPingDelayMs: number;
|
||||
kind: "aki-http";
|
||||
/** Address used by webserver */
|
||||
ip: string;
|
||||
port: number;
|
||||
/** Address used by game client to connect to */
|
||||
backendIp: string;
|
||||
backendPort: string;
|
||||
webSocketPingDelayMs: number;
|
||||
logRequests: boolean;
|
||||
/** e.g. "Aki_Data/Server/images/traders/579dc571d53a0658a154fbec.png": "Aki_Data/Server/images/traders/NewTraderImage.png" */
|
||||
serverImagePathOverride: Record<string, string>;
|
||||
|
@ -42,5 +42,4 @@ export interface Save
|
||||
{
|
||||
/** Should loot gained from raid be saved */
|
||||
loot: boolean;
|
||||
durability: boolean;
|
||||
}
|
||||
|
@ -36,8 +36,10 @@ export interface ILocationConfig extends IBaseConfig
|
||||
/** How full must a random static magazine be %*/
|
||||
minFillStaticMagazinePercent: number;
|
||||
allowDuplicateItemsInStaticContainers: boolean;
|
||||
/** Chance loose/static magazines have ammo in them */
|
||||
/** Chance loose magazines have ammo in them TODO - rename to dynamicMagazineLootHasAmmoChancePercent */
|
||||
magazineLootHasAmmoChancePercent: number;
|
||||
/** Chance static magazines have ammo in them */
|
||||
staticMagazineLootHasAmmoChancePercent: number;
|
||||
/** Key: map, value: loose loot ids to ignore */
|
||||
looseLootBlacklist: Record<string, string[]>;
|
||||
/** Key: map, value: settings to control how long scav raids are*/
|
||||
|
@ -46,6 +46,8 @@ export interface FenceConfig
|
||||
presetSlotsToRemoveChancePercent: Record<string, number>;
|
||||
/** Block seasonal items from appearing when season is inactive */
|
||||
blacklistSeasonalItems: boolean;
|
||||
/** Max pen value allowed to be listed on flea - affects ammo + ammo boxes */
|
||||
ammoMaxPenLimit: number;
|
||||
blacklist: string[];
|
||||
coopExtractGift: CoopExtractReward;
|
||||
btrDeliveryExpireHours: number;
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
import { IBarterScheme } from "@spt-aki/models/eft/common/tables/ITrader";
|
||||
|
||||
export interface ICreateFenceAssortsResult
|
||||
{
|
||||
sptItems: Item[][];
|
||||
barter_scheme: Record<string, IBarterScheme[][]>;
|
||||
loyal_level_items: Record<string, number>;
|
||||
}
|
@ -23,6 +23,13 @@ export class BotDynamicRouter extends DynamicRouter
|
||||
return this.botCallbacks.getBotDifficulty(url, info, sessionID);
|
||||
},
|
||||
),
|
||||
new RouteAction(
|
||||
"/singleplayer/settings/bot/difficulties/",
|
||||
(url: string, info: any, sessionID: string, output: string): any =>
|
||||
{
|
||||
return this.botCallbacks.getAllBotDifficulties(url, info, sessionID);
|
||||
},
|
||||
),
|
||||
new RouteAction(
|
||||
"/singleplayer/settings/bot/maxCap",
|
||||
(url: string, info: any, sessionID: string, output: string): any =>
|
||||
|
@ -20,13 +20,6 @@ export class InraidStaticRouter extends StaticRouter
|
||||
return this.inraidCallbacks.getRaidEndState();
|
||||
},
|
||||
),
|
||||
new RouteAction(
|
||||
"/singleplayer/settings/weapon/durability",
|
||||
(url: string, info: any, sessionID: string, output: string): any =>
|
||||
{
|
||||
return this.inraidCallbacks.getWeaponDurability();
|
||||
},
|
||||
),
|
||||
new RouteAction(
|
||||
"/singleplayer/settings/raid/menu",
|
||||
(url: string, info: any, sessionID: string, output: string): any =>
|
||||
|
@ -45,9 +45,6 @@ export class HttpServer
|
||||
this.handleRequest(req, res);
|
||||
});
|
||||
|
||||
this.databaseServer.getTables().server.ip = this.httpConfig.ip;
|
||||
this.databaseServer.getTables().server.port = this.httpConfig.port;
|
||||
|
||||
/* Config server to listen on a port */
|
||||
httpServer.listen(this.httpConfig.port, this.httpConfig.ip, () =>
|
||||
{
|
||||
@ -82,17 +79,20 @@ export class HttpServer
|
||||
if (this.httpConfig.logRequests)
|
||||
{
|
||||
// TODO: Extend to include 192.168 / 10.10 ranges or check subnet
|
||||
const isLocalRequest = req.socket.remoteAddress.startsWith("127.0.0");
|
||||
if (isLocalRequest)
|
||||
const isLocalRequest = req.socket.remoteAddress?.startsWith("127.0.0");
|
||||
if (typeof isLocalRequest !== "undefined")
|
||||
{
|
||||
this.logger.info(this.localisationService.getText("client_request", req.url));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logger.info(this.localisationService.getText("client_request_ip", {
|
||||
ip: req.socket.remoteAddress,
|
||||
url: req.url.replaceAll("/", "\\"), // Localisation service escapes `/` into hex code `/`
|
||||
}));
|
||||
if (isLocalRequest)
|
||||
{
|
||||
this.logger.info(this.localisationService.getText("client_request", req.url));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logger.info(this.localisationService.getText("client_request_ip", {
|
||||
ip: req.socket.remoteAddress,
|
||||
url: req.url.replaceAll("/", "\\"), // Localisation service escapes `/` into hex code `/`
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,6 +89,12 @@ export class BotLootCacheService
|
||||
case LootCacheType.DRUG_ITEMS:
|
||||
result = this.lootCache[botRole].drugItems;
|
||||
break;
|
||||
case LootCacheType.FOOD_ITEMS:
|
||||
result = this.lootCache[botRole].foodItems;
|
||||
break;
|
||||
case LootCacheType.DRINK_ITEMS:
|
||||
result = this.lootCache[botRole].drinkItems;
|
||||
break;
|
||||
case LootCacheType.STIM_ITEMS:
|
||||
result = this.lootCache[botRole].stimItems;
|
||||
break;
|
||||
@ -219,7 +225,7 @@ export class BotLootCacheService
|
||||
? botJsonTemplate.generation.items.drugs.whitelist
|
||||
: {};
|
||||
|
||||
// no whitelist, find and assign from combined item pool
|
||||
// no drugs whitelist, find and assign from combined item pool
|
||||
if (Object.keys(drugItems).length === 0)
|
||||
{
|
||||
for (const [tpl, weight] of Object.entries(combinedLootPool))
|
||||
@ -232,6 +238,44 @@ export class BotLootCacheService
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted food to bot if any exist
|
||||
const foodItems: Record<string, number> =
|
||||
(Object.keys(botJsonTemplate.generation.items.food.whitelist)?.length > 0)
|
||||
? botJsonTemplate.generation.items.food.whitelist
|
||||
: {};
|
||||
|
||||
// No food whitelist, find and assign from combined item pool
|
||||
if (Object.keys(foodItems).length === 0)
|
||||
{
|
||||
for (const [tpl, weight] of Object.entries(combinedLootPool))
|
||||
{
|
||||
const itemTemplate = this.itemHelper.getItem(tpl)[1];
|
||||
if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.FOOD))
|
||||
{
|
||||
foodItems[tpl] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted drink to bot if any exist
|
||||
const drinkItems: Record<string, number> =
|
||||
(Object.keys(botJsonTemplate.generation.items.food.whitelist)?.length > 0)
|
||||
? botJsonTemplate.generation.items.food.whitelist
|
||||
: {};
|
||||
|
||||
// No drink whitelist, find and assign from combined item pool
|
||||
if (Object.keys(drinkItems).length === 0)
|
||||
{
|
||||
for (const [tpl, weight] of Object.entries(combinedLootPool))
|
||||
{
|
||||
const itemTemplate = this.itemHelper.getItem(tpl)[1];
|
||||
if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.DRINK))
|
||||
{
|
||||
drinkItems[tpl] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign whitelisted stims to bot if any exist
|
||||
const stimItems: Record<string, number> =
|
||||
(Object.keys(botJsonTemplate.generation.items.stims.whitelist)?.length > 0)
|
||||
@ -270,7 +314,7 @@ export class BotLootCacheService
|
||||
}
|
||||
}
|
||||
|
||||
// Get backpack loot (excluding magazines, bullets, grenades and healing items)
|
||||
// Get backpack loot (excluding magazines, bullets, grenades, drink, food and healing/stim items)
|
||||
const filteredBackpackItems = {};
|
||||
for (const itemKey of Object.keys(backpackLootPool))
|
||||
{
|
||||
@ -285,6 +329,8 @@ export class BotLootCacheService
|
||||
|| this.isMagazine(itemTemplate._props)
|
||||
|| this.isMedicalItem(itemTemplate._props)
|
||||
|| this.isGrenade(itemTemplate._props)
|
||||
|| this.isFood(itemTemplate._id)
|
||||
|| this.isDrink(itemTemplate._id)
|
||||
)
|
||||
{
|
||||
// Is type we dont want as backpack loot, skip
|
||||
@ -294,7 +340,7 @@ export class BotLootCacheService
|
||||
filteredBackpackItems[itemKey] = backpackLootPool[itemKey];
|
||||
}
|
||||
|
||||
// Get pocket loot (excluding magazines, bullets, grenades, medical and healing items)
|
||||
// Get pocket loot (excluding magazines, bullets, grenades, drink, food medical and healing/stim items)
|
||||
const filteredPocketItems = {};
|
||||
for (const itemKey of Object.keys(pocketLootPool))
|
||||
{
|
||||
@ -309,6 +355,8 @@ export class BotLootCacheService
|
||||
|| this.isMagazine(itemTemplate._props)
|
||||
|| this.isMedicalItem(itemTemplate._props)
|
||||
|| this.isGrenade(itemTemplate._props)
|
||||
|| this.isFood(itemTemplate._id)
|
||||
|| this.isDrink(itemTemplate._id)
|
||||
|| !("Height" in itemTemplate._props) // lacks height
|
||||
|| !("Width" in itemTemplate._props) // lacks width
|
||||
)
|
||||
@ -319,7 +367,7 @@ export class BotLootCacheService
|
||||
filteredPocketItems[itemKey] = pocketLootPool[itemKey];
|
||||
}
|
||||
|
||||
// Get vest loot (excluding magazines, bullets, grenades, medical and healing items)
|
||||
// Get vest loot (excluding magazines, bullets, grenades, medical and healing/stim items)
|
||||
const filteredVestItems = {};
|
||||
for (const itemKey of Object.keys(vestLootPool))
|
||||
{
|
||||
@ -334,6 +382,8 @@ export class BotLootCacheService
|
||||
|| this.isMagazine(itemTemplate._props)
|
||||
|| this.isMedicalItem(itemTemplate._props)
|
||||
|| this.isGrenade(itemTemplate._props)
|
||||
|| this.isFood(itemTemplate._id)
|
||||
|| this.isDrink(itemTemplate._id)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
@ -344,6 +394,8 @@ export class BotLootCacheService
|
||||
|
||||
this.lootCache[botRole].healingItems = healingItems;
|
||||
this.lootCache[botRole].drugItems = drugItems;
|
||||
this.lootCache[botRole].foodItems = foodItems;
|
||||
this.lootCache[botRole].drinkItems = drinkItems;
|
||||
this.lootCache[botRole].stimItems = stimItems;
|
||||
this.lootCache[botRole].grenadeItems = grenadeItems;
|
||||
|
||||
@ -429,6 +481,16 @@ export class BotLootCacheService
|
||||
return ("ThrowType" in props);
|
||||
}
|
||||
|
||||
protected isFood(tpl: string): boolean
|
||||
{
|
||||
return this.itemHelper.isOfBaseclass(tpl, BaseClasses.FOOD);
|
||||
}
|
||||
|
||||
protected isDrink(tpl: string): boolean
|
||||
{
|
||||
return this.itemHelper.isOfBaseclass(tpl, BaseClasses.DRINK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a bot type exists inside the loot cache
|
||||
* @param botRole role to check for
|
||||
@ -455,6 +517,8 @@ export class BotLootCacheService
|
||||
specialItems: {},
|
||||
grenadeItems: {},
|
||||
drugItems: {},
|
||||
foodItems: {},
|
||||
drinkItems: {},
|
||||
healingItems: {},
|
||||
stimItems: {},
|
||||
};
|
||||
|
@ -3,16 +3,16 @@ import { inject, injectable } from "tsyringe";
|
||||
import { HandbookHelper } from "@spt-aki/helpers/HandbookHelper";
|
||||
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
||||
import { PresetHelper } from "@spt-aki/helpers/PresetHelper";
|
||||
import { MinMax } from "@spt-aki/models/common/MinMax";
|
||||
import { IFenceLevel } from "@spt-aki/models/eft/common/IGlobals";
|
||||
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
||||
import { Item, Repairable, Upd } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
import { Item, Repairable } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
||||
import { IBarterScheme, ITraderAssort } from "@spt-aki/models/eft/common/tables/ITrader";
|
||||
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { Traders } from "@spt-aki/models/enums/Traders";
|
||||
import { IItemDurabilityCurrentMax, ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
|
||||
import { ICreateFenceAssortsResult } from "@spt-aki/models/spt/fence/ICreateFenceAssortsResult";
|
||||
import {
|
||||
IFenceAssortGenerationValues,
|
||||
IGenerationAssortValues,
|
||||
@ -43,7 +43,7 @@ export class FenceService
|
||||
/** Assorts shown on a separate tab when you max out fence rep */
|
||||
protected fenceDiscountAssort: ITraderAssort = undefined;
|
||||
|
||||
/** Hydrated on initial assort generation as part of generateFenceAssorts() */
|
||||
/** Desired baseline counts - Hydrated on initial assort generation as part of generateFenceAssorts() */
|
||||
protected desiredAssortCounts: IFenceAssortGenerationValues;
|
||||
|
||||
constructor(
|
||||
@ -231,16 +231,23 @@ export class FenceService
|
||||
this.deleteRandomAssorts(itemCountToReplace, this.fenceAssort);
|
||||
this.deleteRandomAssorts(discountItemCountToReplace, this.fenceDiscountAssort);
|
||||
|
||||
// Get count of what item pools need new items (item/weapon/equipment)
|
||||
const itemCountsToReplace = this.getCountOfItemsToGenerate();
|
||||
const normalItemCountsToGenerate = this.getItemCountsToGenerate(
|
||||
this.fenceAssort.items,
|
||||
this.desiredAssortCounts.normal,
|
||||
);
|
||||
const newItems = this.createAssorts(normalItemCountsToGenerate, 1);
|
||||
|
||||
const newItems = this.createFenceAssortSkeleton();
|
||||
this.createAssorts(itemCountsToReplace.normal, newItems, 1);
|
||||
this.fenceAssort.items.push(...newItems.items);
|
||||
// Push newly generated assorts into existing data
|
||||
this.updateFenceAssorts(newItems, this.fenceAssort);
|
||||
|
||||
const newDiscountItems = this.createFenceAssortSkeleton();
|
||||
this.createAssorts(itemCountsToReplace.discount, newDiscountItems, 2);
|
||||
this.fenceDiscountAssort.items.push(...newDiscountItems.items);
|
||||
const discountItemCountsToGenerate = this.getItemCountsToGenerate(
|
||||
this.fenceDiscountAssort.items,
|
||||
this.desiredAssortCounts.discount,
|
||||
);
|
||||
const newDiscountItems = this.createAssorts(discountItemCountsToGenerate, 2);
|
||||
|
||||
// Push newly generated discount assorts into existing data
|
||||
this.updateFenceAssorts(newDiscountItems, this.fenceDiscountAssort);
|
||||
|
||||
// Add new barter items to fence barter scheme
|
||||
for (const barterItemKey in newItems.barter_scheme)
|
||||
@ -271,6 +278,46 @@ export class FenceService
|
||||
this.incrementPartialRefreshTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the process of folding new assorts into existing assorts, when a new assort exists already, increment its StackObjectsCount instead
|
||||
* @param newFenceAssorts Assorts to fold into existing fence assorts
|
||||
* @param existingFenceAssorts Current fence assorts new assorts will be added to
|
||||
*/
|
||||
protected updateFenceAssorts(newFenceAssorts: ICreateFenceAssortsResult, existingFenceAssorts: ITraderAssort): void
|
||||
{
|
||||
for (const itemWithChildren of newFenceAssorts.sptItems)
|
||||
{
|
||||
// Find the root item
|
||||
const newRootItem = itemWithChildren.find((item) => item.slotId === "hideout");
|
||||
|
||||
// Find a matching root item with same tpl in existing assort
|
||||
const existingRootItem = existingFenceAssorts.items.find((item) =>
|
||||
item._tpl === newRootItem._tpl && item.slotId === "hideout"
|
||||
);
|
||||
|
||||
// Check if same type of item exists + its on list of item types to always stack
|
||||
if (existingRootItem && this.itemInPreventDupeCategoryList(newRootItem._tpl))
|
||||
{
|
||||
// Guard against a missing stack count
|
||||
if (!existingRootItem.upd.StackObjectsCount)
|
||||
{
|
||||
existingRootItem.upd.StackObjectsCount = 1;
|
||||
}
|
||||
|
||||
// Merge new items count into existing, dont add new loyalty/barter data as it already exists
|
||||
existingRootItem.upd.StackObjectsCount += newRootItem.upd.StackObjectsCount;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// New assort to be added to existing assorts
|
||||
existingFenceAssorts.items.push(...itemWithChildren);
|
||||
existingFenceAssorts.barter_scheme[newRootItem._id] = newFenceAssorts.barter_scheme[newRootItem._id];
|
||||
existingFenceAssorts.loyal_level_items[newRootItem._id] =
|
||||
newFenceAssorts.loyal_level_items[newRootItem._id];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment fence next refresh timestamp by current timestamp + partialRefreshTimeSeconds from config
|
||||
*/
|
||||
@ -281,18 +328,18 @@ export class FenceService
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the current fence offer count to what the config wants it to be,
|
||||
* If value is lower add extra count to value to generate more items to fill gap
|
||||
* @param existingItemCountToReplace count of items to generate
|
||||
* @returns number of items to generate
|
||||
* Get values that will hydrate the passed in assorts back to the desired counts
|
||||
* @param assortItems Current assorts after items have been removed
|
||||
* @param generationValues Base counts assorts should be adjusted to
|
||||
* @returns IGenerationAssortValues object with adjustments needed to reach desired state
|
||||
*/
|
||||
protected getCountOfItemsToGenerate(): IFenceAssortGenerationValues
|
||||
protected getItemCountsToGenerate(
|
||||
assortItems: Item[],
|
||||
generationValues: IGenerationAssortValues,
|
||||
): IGenerationAssortValues
|
||||
{
|
||||
const currentItemAssortCount = Object.keys(this.fenceAssort.loyal_level_items).length;
|
||||
|
||||
const rootPresetItems = this.fenceAssort.items.filter((item) =>
|
||||
item.slotId === "hideout" && item.upd.sptPresetId
|
||||
);
|
||||
const allRootItems = assortItems.filter((item) => item.slotId === "hideout");
|
||||
const rootPresetItems = allRootItems.filter((item) => item.upd.sptPresetId);
|
||||
|
||||
// Get count of weapons
|
||||
const currentWeaponPresetCount = rootPresetItems.reduce((count, item) =>
|
||||
@ -306,60 +353,19 @@ export class FenceService
|
||||
return this.itemHelper.armorItemCanHoldMods(item._tpl) ? count + 1 : count;
|
||||
}, 0);
|
||||
|
||||
const itemCountToGenerate = Math.max(this.desiredAssortCounts.normal.item - currentItemAssortCount, 0);
|
||||
const weaponCountToGenerate = Math.max(
|
||||
this.desiredAssortCounts.normal.weaponPreset - currentWeaponPresetCount,
|
||||
0,
|
||||
);
|
||||
const equipmentCountToGenerate = Math.max(
|
||||
this.desiredAssortCounts.normal.equipmentPreset - currentEquipmentPresetCount,
|
||||
0,
|
||||
);
|
||||
// Normal item count is total count minus weapon + armor count
|
||||
const nonPresetItemAssortCount = allRootItems.length - (currentWeaponPresetCount + currentEquipmentPresetCount);
|
||||
|
||||
const normalValues: IGenerationAssortValues = {
|
||||
// Get counts of items to generate, never let values fall below 0
|
||||
const itemCountToGenerate = Math.max(generationValues.item - nonPresetItemAssortCount, 0);
|
||||
const weaponCountToGenerate = Math.max(generationValues.weaponPreset - currentWeaponPresetCount, 0);
|
||||
const equipmentCountToGenerate = Math.max(generationValues.equipmentPreset - currentEquipmentPresetCount, 0);
|
||||
|
||||
return {
|
||||
item: itemCountToGenerate,
|
||||
weaponPreset: weaponCountToGenerate,
|
||||
equipmentPreset: equipmentCountToGenerate,
|
||||
};
|
||||
|
||||
// Discount tab handling
|
||||
const currentDiscountItemAssortCount = Object.keys(this.fenceDiscountAssort.loyal_level_items).length;
|
||||
const rootDiscountPresetItems = this.fenceDiscountAssort.items.filter((item) =>
|
||||
item.slotId === "hideout" && item.upd.sptPresetId
|
||||
);
|
||||
|
||||
// Get count of weapons
|
||||
const currentDiscountWeaponPresetCount = rootDiscountPresetItems.reduce((count, item) =>
|
||||
{
|
||||
return this.itemHelper.isOfBaseclass(item._tpl, BaseClasses.WEAPON) ? count + 1 : count;
|
||||
}, 0);
|
||||
|
||||
// Get count of equipment
|
||||
const currentDiscountEquipmentPresetCount = rootDiscountPresetItems.reduce((count, item) =>
|
||||
{
|
||||
return this.itemHelper.armorItemCanHoldMods(item._tpl) ? count + 1 : count;
|
||||
}, 0);
|
||||
|
||||
const itemDiscountCountToGenerate = Math.max(
|
||||
this.desiredAssortCounts.discount.item - currentDiscountItemAssortCount,
|
||||
0,
|
||||
);
|
||||
const weaponDiscountCountToGenerate = Math.max(
|
||||
this.desiredAssortCounts.discount.weaponPreset - currentDiscountWeaponPresetCount,
|
||||
0,
|
||||
);
|
||||
const equipmentDiscountCountToGenerate = Math.max(
|
||||
this.desiredAssortCounts.discount.equipmentPreset - currentDiscountEquipmentPresetCount,
|
||||
0,
|
||||
);
|
||||
|
||||
const discountValues: IGenerationAssortValues = {
|
||||
item: itemDiscountCountToGenerate,
|
||||
weaponPreset: weaponDiscountCountToGenerate,
|
||||
equipmentPreset: equipmentDiscountCountToGenerate,
|
||||
};
|
||||
|
||||
return { normal: normalValues, discount: discountValues };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -386,18 +392,26 @@ export class FenceService
|
||||
*/
|
||||
protected removeRandomItemFromAssorts(assort: ITraderAssort, rootItems: Item[]): void
|
||||
{
|
||||
const rootItemToRemove = this.randomUtil.getArrayValue(rootItems);
|
||||
|
||||
// Clean up any mods if item had them
|
||||
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(assort.items, rootItemToRemove._id);
|
||||
for (const itemToDelete of itemWithChildren)
|
||||
{
|
||||
// Delete item from assort items array
|
||||
assort.items.splice(assort.items.indexOf(itemToDelete), 1);
|
||||
const rootItemToAdjust = this.randomUtil.getArrayValue(rootItems);
|
||||
const itemCountToRemove = this.randomUtil.getInt(1, rootItemToAdjust.upd.StackObjectsCount);
|
||||
if (itemCountToRemove > 1 && itemCountToRemove < rootItemToAdjust.upd.StackObjectsCount)
|
||||
{ // More than 1 + less then full stack
|
||||
// Reduce stack size but keep stack
|
||||
rootItemToAdjust.upd.StackObjectsCount -= itemCountToRemove;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove up item + any mods
|
||||
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(assort.items, rootItemToAdjust._id);
|
||||
for (const itemToDelete of itemWithChildren)
|
||||
{
|
||||
// Delete item from assort items array
|
||||
assort.items.splice(assort.items.indexOf(itemToDelete), 1);
|
||||
}
|
||||
|
||||
delete assort.barter_scheme[rootItemToRemove._id];
|
||||
delete assort.loyal_level_items[rootItemToRemove._id];
|
||||
delete assort.barter_scheme[rootItemToAdjust._id];
|
||||
delete assort.loyal_level_items[rootItemToAdjust._id];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -437,16 +451,35 @@ export class FenceService
|
||||
this.createInitialFenceAssortGenerationValues();
|
||||
|
||||
// Create basic fence assort
|
||||
const assorts = this.createFenceAssortSkeleton();
|
||||
this.createAssorts(this.desiredAssortCounts.normal, assorts, 1);
|
||||
const assorts = this.createAssorts(this.desiredAssortCounts.normal, 1);
|
||||
|
||||
// Store in this.fenceAssort
|
||||
this.setFenceAssort(assorts);
|
||||
this.setFenceAssort(this.convertIntoFenceAssort(assorts));
|
||||
|
||||
// Create level 2 assorts accessible at rep level 6
|
||||
const discountAssorts = this.createFenceAssortSkeleton();
|
||||
this.createAssorts(this.desiredAssortCounts.discount, discountAssorts, 2);
|
||||
const discountAssorts = this.createAssorts(this.desiredAssortCounts.discount, 2);
|
||||
|
||||
// Store in this.fenceDiscountAssort
|
||||
this.setFenceDiscountAssort(discountAssorts);
|
||||
this.setFenceDiscountAssort(this.convertIntoFenceAssort(discountAssorts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the intermediary assort data generated into format client can process
|
||||
* @param intermediaryAssorts Generated assorts that will be converted
|
||||
* @returns ITraderAssort
|
||||
*/
|
||||
protected convertIntoFenceAssort(intermediaryAssorts: ICreateFenceAssortsResult): ITraderAssort
|
||||
{
|
||||
const result = this.createFenceAssortSkeleton();
|
||||
for (const itemWithChilden of intermediaryAssorts.sptItems)
|
||||
{
|
||||
result.items.push(...itemWithChilden);
|
||||
}
|
||||
|
||||
result.barter_scheme = intermediaryAssorts.barter_scheme;
|
||||
result.loyal_level_items = intermediaryAssorts.loyal_level_items;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -506,14 +539,22 @@ export class FenceService
|
||||
* @param assortCount Number of assorts to generate
|
||||
* @param assorts object to add created assorts to
|
||||
*/
|
||||
protected createAssorts(itemCounts: IGenerationAssortValues, assorts: ITraderAssort, loyaltyLevel: number): void
|
||||
protected createAssorts(itemCounts: IGenerationAssortValues, loyaltyLevel: number): ICreateFenceAssortsResult
|
||||
{
|
||||
const result: ICreateFenceAssortsResult = { sptItems: [], barter_scheme: {}, loyal_level_items: {} };
|
||||
|
||||
const baseFenceAssortClone = this.jsonUtil.clone(this.databaseServer.getTables().traders[Traders.FENCE].assort);
|
||||
const itemTypeLimitCounts = this.initItemLimitCounter(this.traderConfig.fence.itemTypeLimits);
|
||||
|
||||
if (itemCounts.item > 0)
|
||||
{
|
||||
this.addItemAssorts(itemCounts.item, assorts, baseFenceAssortClone, itemTypeLimitCounts, loyaltyLevel);
|
||||
const itemResult = this.addItemAssorts(
|
||||
itemCounts.item,
|
||||
result,
|
||||
baseFenceAssortClone,
|
||||
itemTypeLimitCounts,
|
||||
loyaltyLevel,
|
||||
);
|
||||
}
|
||||
|
||||
if (itemCounts.weaponPreset > 0 || itemCounts.equipmentPreset > 0)
|
||||
@ -522,11 +563,13 @@ export class FenceService
|
||||
this.addPresetsToAssort(
|
||||
itemCounts.weaponPreset,
|
||||
itemCounts.equipmentPreset,
|
||||
assorts,
|
||||
result,
|
||||
baseFenceAssortClone,
|
||||
loyaltyLevel,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -539,15 +582,15 @@ export class FenceService
|
||||
*/
|
||||
protected addItemAssorts(
|
||||
assortCount: number,
|
||||
assorts: ITraderAssort,
|
||||
assorts: ICreateFenceAssortsResult,
|
||||
baseFenceAssortClone: ITraderAssort,
|
||||
itemTypeLimits: Record<string, { current: number; max: number; }>,
|
||||
loyaltyLevel: number,
|
||||
): void
|
||||
{
|
||||
const priceLimits = this.traderConfig.fence.itemCategoryRoublePriceLimit;
|
||||
const assortRootItems = baseFenceAssortClone.items.filter((x) =>
|
||||
x.parentId === "hideout" && !x.upd?.sptPresetId
|
||||
const assortRootItems = baseFenceAssortClone.items.filter((item) =>
|
||||
item.parentId === "hideout" && !item.upd?.sptPresetId
|
||||
);
|
||||
|
||||
for (let i = 0; i < assortCount; i++)
|
||||
@ -614,7 +657,7 @@ export class FenceService
|
||||
}
|
||||
|
||||
// Skip items already in the assort if it exists in the prevent duplicate list
|
||||
const existingItemThatMatches = this.getMatchingItem(rootItemBeingAdded, itemDbDetails, assorts.items);
|
||||
const existingItemThatMatches = this.getMatchingItem(rootItemBeingAdded, itemDbDetails, assorts.sptItems);
|
||||
const shouldBeStacked = this.itemShouldBeForceStacked(existingItemThatMatches, itemDbDetails);
|
||||
if (shouldBeStacked && existingItemThatMatches)
|
||||
{ // Decrement loop counter so another items gets added
|
||||
@ -630,7 +673,7 @@ export class FenceService
|
||||
this.randomiseArmorModDurability(desiredAssortItemAndChildrenClone, itemDbDetails);
|
||||
}
|
||||
|
||||
assorts.items.push(...desiredAssortItemAndChildrenClone);
|
||||
assorts.sptItems.push(desiredAssortItemAndChildrenClone);
|
||||
|
||||
assorts.barter_scheme[rootItemBeingAdded._id] = this.jsonUtil.clone(
|
||||
baseFenceAssortClone.barter_scheme[chosenBaseAssortRoot._id],
|
||||
@ -651,15 +694,15 @@ export class FenceService
|
||||
* e.g. salewa hp resource units left
|
||||
* @param rootItemBeingAdded item to look for a match against
|
||||
* @param itemDbDetails Db details of matching item
|
||||
* @param fenceItemAssorts Items to search through
|
||||
* @param itemsWithChildren Items to search through
|
||||
* @returns Matching assort item
|
||||
*/
|
||||
protected getMatchingItem(rootItemBeingAdded: Item, itemDbDetails: ITemplateItem, fenceItemAssorts: Item[]): Item
|
||||
protected getMatchingItem(rootItemBeingAdded: Item, itemDbDetails: ITemplateItem, itemsWithChildren: Item[][]): Item
|
||||
{
|
||||
// Get matching root items
|
||||
const matchingItems = fenceItemAssorts.filter((item) =>
|
||||
item._tpl === rootItemBeingAdded._tpl && item.parentId === "hideout"
|
||||
);
|
||||
const matchingItems = itemsWithChildren.filter((itemWithChildren) =>
|
||||
itemWithChildren.find((item) => item._tpl === rootItemBeingAdded._tpl && item.parentId === "hideout")
|
||||
).flatMap((x) => x);
|
||||
if (matchingItems.length === 0)
|
||||
{
|
||||
// Nothing matches by tpl and is root item, exit early
|
||||
@ -726,11 +769,13 @@ export class FenceService
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.itemInPreventDupeCategoryList(itemDbDetails._id);
|
||||
}
|
||||
|
||||
protected itemInPreventDupeCategoryList(tpl: string): boolean
|
||||
{
|
||||
// Item type in config list
|
||||
return this.itemHelper.isOfBaseclasses(
|
||||
itemDbDetails._id,
|
||||
this.traderConfig.fence.preventDuplicateOffersOfCategory,
|
||||
);
|
||||
return this.itemHelper.isOfBaseclasses(tpl, this.traderConfig.fence.preventDuplicateOffersOfCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -799,7 +844,7 @@ export class FenceService
|
||||
protected addPresetsToAssort(
|
||||
desiredWeaponPresetsCount: number,
|
||||
desiredEquipmentPresetsCount: number,
|
||||
assorts: ITraderAssort,
|
||||
assorts: ICreateFenceAssortsResult,
|
||||
baseFenceAssort: ITraderAssort,
|
||||
loyaltyLevel: number,
|
||||
): void
|
||||
@ -848,7 +893,7 @@ export class FenceService
|
||||
// Remapping IDs causes parentid to be altered
|
||||
presetWithChildrenClone[0].parentId = "hideout";
|
||||
|
||||
assorts.items.push(...presetWithChildrenClone);
|
||||
assorts.sptItems.push(presetWithChildrenClone);
|
||||
|
||||
// Set assort price
|
||||
// Must be careful to use correct id as the item has had its IDs regenerated
|
||||
@ -908,7 +953,7 @@ export class FenceService
|
||||
// Remapping IDs causes parentid to be altered
|
||||
presetWithChildrenClone[0].parentId = "hideout";
|
||||
|
||||
assorts.items.push(...presetWithChildrenClone);
|
||||
assorts.sptItems.push(presetWithChildrenClone);
|
||||
|
||||
// Set assort price
|
||||
// Must be careful to use correct id as the item has had its IDs regenerated
|
||||
|
@ -13,6 +13,7 @@ import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||
export class ItemBaseClassService
|
||||
{
|
||||
protected itemBaseClassesCache: Record<string, string[]> = {};
|
||||
protected items: Record<string, ITemplateItem>;
|
||||
protected cacheGenerated = false;
|
||||
|
||||
constructor(
|
||||
@ -31,15 +32,15 @@ export class ItemBaseClassService
|
||||
// Clear existing cache
|
||||
this.itemBaseClassesCache = {};
|
||||
|
||||
const allDbItems = this.databaseServer.getTables().templates.items;
|
||||
if (!allDbItems)
|
||||
this.items = this.databaseServer.getTables().templates.items;
|
||||
if (!this.items)
|
||||
{
|
||||
this.logger.warning(this.localisationService.getText("baseclass-missing_db_no_cache"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredDbItems = Object.values(allDbItems).filter((x) => x._type === "Item");
|
||||
const filteredDbItems = Object.values(this.items).filter((x) => x._type === "Item");
|
||||
for (const item of filteredDbItems)
|
||||
{
|
||||
const itemIdToUpdate = item._id;
|
||||
@ -48,7 +49,7 @@ export class ItemBaseClassService
|
||||
this.itemBaseClassesCache[item._id] = [];
|
||||
}
|
||||
|
||||
this.addBaseItems(itemIdToUpdate, item, allDbItems);
|
||||
this.addBaseItems(itemIdToUpdate, item);
|
||||
}
|
||||
|
||||
this.cacheGenerated = true;
|
||||
@ -58,16 +59,15 @@ export class ItemBaseClassService
|
||||
* Helper method, recursivly iterate through items parent items, finding and adding ids to dictionary
|
||||
* @param itemIdToUpdate item tpl to store base ids against in dictionary
|
||||
* @param item item being checked
|
||||
* @param allDbItems all items in db
|
||||
*/
|
||||
protected addBaseItems(itemIdToUpdate: string, item: ITemplateItem, allDbItems: Record<string, ITemplateItem>): void
|
||||
protected addBaseItems(itemIdToUpdate: string, item: ITemplateItem): void
|
||||
{
|
||||
this.itemBaseClassesCache[itemIdToUpdate].push(item._parent);
|
||||
const parent = allDbItems[item._parent];
|
||||
const parent = this.items[item._parent];
|
||||
|
||||
if (parent._parent !== "")
|
||||
{
|
||||
this.addBaseItems(itemIdToUpdate, parent, allDbItems);
|
||||
this.addBaseItems(itemIdToUpdate, parent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,8 +91,9 @@ export class ItemBaseClassService
|
||||
return false;
|
||||
}
|
||||
|
||||
// Edge case - this is the 'root' item that all other items inherit from
|
||||
if (itemTpl === BaseClasses.ITEM)
|
||||
// The cache is only generated for item templates with `_type === "Item"`, so return false for any other type,
|
||||
// including item templates that simply don't exist.
|
||||
if (!this.cachedItemIsOfItemType(itemTpl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -114,6 +115,16 @@ export class ItemBaseClassService
|
||||
return this.itemBaseClassesCache[itemTpl].some((x) => baseClasses.includes(x));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cached item template is of type Item
|
||||
* @param itemTemplateId item to check
|
||||
* @returns true if item is of type Item
|
||||
*/
|
||||
private cachedItemIsOfItemType(itemTemplateId: string): boolean
|
||||
{
|
||||
return this.items[itemTemplateId]?._type === "Item";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base classes item inherits from
|
||||
* @param itemTpl item to get base classes for
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user