Merge branch 'master' into 3.8.0

# Conflicts:
#	project/.vscode/launch.json
#	project/assets/database/locations/bigmap/base.json
#	project/assets/database/locations/interchange/base.json
#	project/assets/database/locations/rezervbase/base.json
#	project/gulpfile.mjs
#	project/package.json
#	project/src/ErrorHandler.ts
#	project/src/Program.ts
#	project/src/callbacks/DataCallbacks.ts
#	project/src/callbacks/DialogueCallbacks.ts
#	project/src/callbacks/GameCallbacks.ts
#	project/src/callbacks/HandbookCallbacks.ts
#	project/src/callbacks/HealthCallbacks.ts
#	project/src/callbacks/HttpCallbacks.ts
#	project/src/callbacks/LauncherCallbacks.ts
#	project/src/callbacks/LocationCallbacks.ts
#	project/src/callbacks/MatchCallbacks.ts
#	project/src/callbacks/ModCallbacks.ts
#	project/src/callbacks/NotifierCallbacks.ts
#	project/src/callbacks/PresetCallbacks.ts
#	project/src/callbacks/ProfileCallbacks.ts
#	project/src/callbacks/RagfairCallbacks.ts
#	project/src/callbacks/TraderCallbacks.ts
#	project/src/context/ApplicationContext.ts
#	project/src/context/ContextVariableType.ts
#	project/src/controllers/BotController.ts
#	project/src/controllers/CustomizationController.ts
#	project/src/controllers/DialogueController.ts
#	project/src/controllers/GameController.ts
#	project/src/controllers/HealthController.ts
#	project/src/controllers/HideoutController.ts
#	project/src/controllers/InraidController.ts
#	project/src/controllers/InsuranceController.ts
#	project/src/controllers/InventoryController.ts
#	project/src/controllers/LauncherController.ts
#	project/src/controllers/LocationController.ts
#	project/src/controllers/MatchController.ts
#	project/src/controllers/QuestController.ts
#	project/src/controllers/RagfairController.ts
#	project/src/controllers/RepeatableQuestController.ts
#	project/src/controllers/TradeController.ts
#	project/src/di/Container.ts
#	project/src/di/Router.ts
#	project/src/generators/BotEquipmentModGenerator.ts
#	project/src/generators/BotLevelGenerator.ts
#	project/src/generators/BotWeaponGenerator.ts
#	project/src/generators/LocationGenerator.ts
#	project/src/generators/LootGenerator.ts
#	project/src/generators/RepeatableQuestGenerator.ts
#	project/src/generators/WeatherGenerator.ts
#	project/src/generators/weapongen/InventoryMagGen.ts
#	project/src/generators/weapongen/implementations/BarrelInventoryMagGen.ts
#	project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts
#	project/src/helpers/AssortHelper.ts
#	project/src/helpers/BotGeneratorHelper.ts
#	project/src/helpers/InRaidHelper.ts
#	project/src/helpers/ProfileHelper.ts
#	project/src/helpers/RagfairHelper.ts
#	project/src/helpers/RagfairOfferHelper.ts
#	project/src/helpers/TraderHelper.ts
#	project/src/loaders/ModLoadOrder.ts
#	project/src/loaders/PostDBModLoader.ts
#	project/src/loaders/PreAkiModLoader.ts
#	project/src/models/eft/common/IGlobals.ts
#	project/src/models/eft/common/ILocationBase.ts
#	project/src/models/eft/common/tables/IBotBase.ts
#	project/src/models/eft/common/tables/IProfileTemplate.ts
#	project/src/models/eft/common/tables/ITemplateItem.ts
#	project/src/models/eft/dialog/IAcceptFriendRequestData.ts
#	project/src/models/eft/dialog/IDeleteFriendRequest.ts
#	project/src/models/eft/game/IGameConfigResponse.ts
#	project/src/models/eft/game/IGameKeepAliveResponse.ts
#	project/src/models/eft/game/IGameStartResponse.ts
#	project/src/models/eft/match/IJoinMatchResult.ts
#	project/src/models/eft/notifier/INotifier.ts
#	project/src/models/eft/profile/GetProfileStatusResponseData.ts
#	project/src/models/eft/trade/IProcessBuyTradeRequestData.ts
#	project/src/models/eft/trade/IProcessSellTradeRequestData.ts
#	project/src/models/enums/WildSpawnTypeNumber.ts
#	project/src/models/spt/bots/BotGenerationDetails.ts
#	project/src/models/spt/config/IBotConfig.ts
#	project/src/models/spt/config/IBotDurability.ts
#	project/src/models/spt/config/IInRaidConfig.ts
#	project/src/models/spt/config/ILocationConfig.ts
#	project/src/models/spt/config/IQuestConfig.ts
#	project/src/models/spt/config/ISeasonalEventConfig.ts
#	project/src/models/spt/server/ILocations.ts
#	project/src/models/spt/utils/IUuidGenerator.ts
#	project/src/routers/dynamic/BotDynamicRouter.ts
#	project/src/routers/dynamic/BundleDynamicRouter.ts
#	project/src/routers/dynamic/CustomizationDynamicRouter.ts
#	project/src/routers/dynamic/DataDynamicRouter.ts
#	project/src/routers/dynamic/HttpDynamicRouter.ts
#	project/src/routers/dynamic/InraidDynamicRouter.ts
#	project/src/routers/dynamic/LocationDynamicRouter.ts
#	project/src/routers/dynamic/NotifierDynamicRouter.ts
#	project/src/routers/dynamic/TraderDynamicRouter.ts
#	project/src/routers/save_load/InsuranceSaveLoadRouter.ts
#	project/src/routers/save_load/ProfileSaveLoadRouter.ts
#	project/src/routers/serializers/NotifySerializer.ts
#	project/src/routers/static/BotStaticRouter.ts
#	project/src/routers/static/BundleStaticRouter.ts
#	project/src/routers/static/ClientLogStaticRouter.ts
#	project/src/routers/static/CustomizationStaticRouter.ts
#	project/src/routers/static/DataStaticRouter.ts
#	project/src/routers/static/DialogStaticRouter.ts
#	project/src/routers/static/GameStaticRouter.ts
#	project/src/routers/static/HealthStaticRouter.ts
#	project/src/routers/static/InraidStaticRouter.ts
#	project/src/routers/static/InsuranceStaticRouter.ts
#	project/src/routers/static/ItemEventStaticRouter.ts
#	project/src/routers/static/LauncherStaticRouter.ts
#	project/src/routers/static/LocationStaticRouter.ts
#	project/src/routers/static/MatchStaticRouter.ts
#	project/src/routers/static/NotifierStaticRouter.ts
#	project/src/routers/static/PresetStaticRouter.ts
#	project/src/routers/static/ProfileStaticRouter.ts
#	project/src/routers/static/QuestStaticRouter.ts
#	project/src/routers/static/RagfairStaticRouter.ts
#	project/src/routers/static/TraderStaticRouter.ts
#	project/src/routers/static/WeatherStaticRouter.ts
#	project/src/services/BotEquipmentFilterService.ts
#	project/src/services/BotGenerationCacheService.ts
#	project/src/services/BotWeaponModLimitService.ts
#	project/src/services/PaymentService.ts
#	project/src/services/ProfileFixerService.ts
#	project/src/services/RagfairOfferService.ts
#	project/src/services/RagfairTaxService.ts
#	project/src/services/RepairService.ts
#	project/src/services/SeasonalEventService.ts
#	project/src/utils/RagfairOfferHolder.ts
#	project/src/utils/TimeUtil.ts
#	project/src/utils/UUidGenerator.ts
#	project/src/utils/VFS.ts
#	project/src/utils/collections/queue/Queue.ts
#	project/src/utils/logging/AbstractWinstonLogger.ts
#	project/src/utils/logging/WinstonMainLogger.ts
#	project/src/utils/logging/WinstonRequestLogger.ts
#	project/tests/utils/TimeUtil.test.ts

Manually resolved by Refringe.
This commit is contained in:
Refringe 2023-11-16 23:35:11 -05:00
commit 6cd86e67b0
No known key found for this signature in database
GPG Key ID: 64E03E5F892C6F9E
45 changed files with 42224 additions and 44853 deletions

View File

@ -800,14 +800,12 @@
"_tpl": "5fd4c4fa16cac650092f6771", "_tpl": "5fd4c4fa16cac650092f6771",
"parentId": "652a6f5c35258f985804aa0b", "parentId": "652a6f5c35258f985804aa0b",
"slotId": "main" "slotId": "main"
}, }, {
{
"_id": "652a6f5c35258f985804aa0d", "_id": "652a6f5c35258f985804aa0d",
"_tpl": "64be79c487d1510151095552", "_tpl": "64be79c487d1510151095552",
"parentId": "652a6f5c35258f985804aa0b", "parentId": "652a6f5c35258f985804aa0b",
"slotId": "main" "slotId": "main"
}, }, {
{
"_id": "652a6f5c35258f985804aa0e", "_id": "652a6f5c35258f985804aa0e",
"_tpl": "643ea5b23db6f9f57107d9fd", "_tpl": "643ea5b23db6f9f57107d9fd",
"upd": { "upd": {
@ -818,62 +816,52 @@
}, },
"parentId": "652a6f5c35258f985804aa0b", "parentId": "652a6f5c35258f985804aa0b",
"slotId": "main" "slotId": "main"
}, }, {
{
"_id": "652a6f5c35258f985804aa0f", "_id": "652a6f5c35258f985804aa0f",
"_tpl": "6410745d5dd49d77bd078485", "_tpl": "6410745d5dd49d77bd078485",
"parentId": "652a6f5c35258f985804aa0e", "parentId": "652a6f5c35258f985804aa0e",
"slotId": "mod_stock" "slotId": "mod_stock"
}, }, {
{
"_id": "652a6f5c35258f985804aa10", "_id": "652a6f5c35258f985804aa10",
"_tpl": "6410758c857473525b08bb77", "_tpl": "6410758c857473525b08bb77",
"parentId": "652a6f5c35258f985804aa0e", "parentId": "652a6f5c35258f985804aa0e",
"slotId": "mod_barrel" "slotId": "mod_barrel"
}, }, {
{
"_id": "652a6f5c35258f985804aa11", "_id": "652a6f5c35258f985804aa11",
"_tpl": "64119d1f2c6d6f921a0929f8", "_tpl": "64119d1f2c6d6f921a0929f8",
"parentId": "652a6f5c35258f985804aa10", "parentId": "652a6f5c35258f985804aa10",
"slotId": "mod_muzzle" "slotId": "mod_muzzle"
}, }, {
{
"_id": "652a6f5c35258f985804aa12", "_id": "652a6f5c35258f985804aa12",
"_tpl": "64119d672c6d6f921a0929fb", "_tpl": "64119d672c6d6f921a0929fb",
"parentId": "652a6f5c35258f985804aa11", "parentId": "652a6f5c35258f985804aa11",
"slotId": "mod_sight_front" "slotId": "mod_sight_front"
}, }, {
{
"_id": "652a6f5c35258f985804aa13", "_id": "652a6f5c35258f985804aa13",
"_tpl": "64119d90dcf48d656f0aa275", "_tpl": "64119d90dcf48d656f0aa275",
"parentId": "652a6f5c35258f985804aa10", "parentId": "652a6f5c35258f985804aa10",
"slotId": "mod_sight_rear" "slotId": "mod_sight_rear"
}, }, {
{
"_id": "652a6f5c35258f985804aa14", "_id": "652a6f5c35258f985804aa14",
"_tpl": "64119cdbdcf48d656f0aa272", "_tpl": "64119cdbdcf48d656f0aa272",
"parentId": "652a6f5c35258f985804aa0e", "parentId": "652a6f5c35258f985804aa0e",
"slotId": "mod_reciever" "slotId": "mod_reciever"
}, }, {
{
"_id": "652a6f5c35258f985804aa15", "_id": "652a6f5c35258f985804aa15",
"_tpl": "6422e1ea3c0f06190302161a", "_tpl": "6422e1ea3c0f06190302161a",
"parentId": "652a6f5c35258f985804aa0e", "parentId": "652a6f5c35258f985804aa0e",
"slotId": "mod_magazine" "slotId": "mod_magazine"
}, }, {
{
"_id": "652a6f5c35258f985804aa16", "_id": "652a6f5c35258f985804aa16",
"_tpl": "641dc35e19604f20c800be18", "_tpl": "641dc35e19604f20c800be18",
"parentId": "652a6f5c35258f985804aa0e", "parentId": "652a6f5c35258f985804aa0e",
"slotId": "mod_scope" "slotId": "mod_scope"
}, }, {
{
"_id": "652a6f5c35258f985804aa17", "_id": "652a6f5c35258f985804aa17",
"_tpl": "5b3f7c1c5acfc40dc5296b1d", "_tpl": "5b3f7c1c5acfc40dc5296b1d",
"parentId": "652a6f5c35258f985804aa16", "parentId": "652a6f5c35258f985804aa16",
"slotId": "mod_scope" "slotId": "mod_scope"
}, }, {
{
"_id": "652a6f5c35258f985804aa18", "_id": "652a6f5c35258f985804aa18",
"_tpl": "64748cb8de82c85eaf0a273a", "_tpl": "64748cb8de82c85eaf0a273a",
"upd": { "upd": {
@ -884,32 +872,332 @@
}, },
"parentId": "652a6f5c35258f985804aa0b", "parentId": "652a6f5c35258f985804aa0b",
"slotId": "main" "slotId": "main"
}, }, {
{
"_id": "652a6f5c35258f985804aa19", "_id": "652a6f5c35258f985804aa19",
"_tpl": "64748d02d1c009260702b526", "_tpl": "64748d02d1c009260702b526",
"parentId": "652a6f5c35258f985804aa18", "parentId": "652a6f5c35258f985804aa18",
"slotId": "mod_barrel" "slotId": "mod_barrel"
}, }, {
{
"_id": "652a6f5c35258f985804aa1a", "_id": "652a6f5c35258f985804aa1a",
"_tpl": "5f60cd6cf2bcbb675b00dac6", "_tpl": "5f60cd6cf2bcbb675b00dac6",
"parentId": "652a6f5c35258f985804aa0b", "parentId": "652a6f5c35258f985804aa0b",
"slotId": "main" "slotId": "main"
}, }, {
{
"_id": "652a6f5c35258f985804aa1b", "_id": "652a6f5c35258f985804aa1b",
"_tpl": "5d5e7d28a4b936645d161203", "_tpl": "5d5e7d28a4b936645d161203",
"parentId": "652a6f5c35258f985804aa0b", "parentId": "652a6f5c35258f985804aa0b",
"slotId": "main" "slotId": "main"
}, }, {
{
"_id": "652a6f5c35258f985804aa1c", "_id": "652a6f5c35258f985804aa1c",
"_tpl": "5d1b376e86f774252519444e", "_tpl": "5d1b376e86f774252519444e",
"parentId": "652a6f5c35258f985804aa0b", "parentId": "652a6f5c35258f985804aa0b",
"slotId": "main" "slotId": "main"
} }
] ]
},
"GROUNDZERO": {
"items": [{
"_id": "65539de6fe677db90c0f53cb",
"_tpl": "619cbf7d23893217ec30b689",
"slotId": "main"
}
],
"sender": "System",
"messageText": "GROUNDZERO",
"collectionTimeHours": 48,
"associatedEvent": "Promo"
},
"IAMMIGHTY": {
"items": [{
"_id": "6553a38f2c33e9939d0a2391",
"_tpl": "6176aca650224f204c1da3fb",
"upd": {
"FireMode": {
"FireMode": "single"
}
},
"parentId": "6553a38f2c33e9939d0a2390",
"slotId": "main"
}, {
"_id": "6553a38f2c33e9939d0a2392",
"_tpl": "6193dcd0f8ee7e52e4210a28",
"parentId": "6553a38f2c33e9939d0a2391",
"slotId": "mod_pistol_grip"
}, {
"_id": "6553a38f2c33e9939d0a2393",
"_tpl": "617131a4568c120fdd29482d",
"parentId": "6553a38f2c33e9939d0a2391",
"slotId": "mod_magazine"
}, {
"_id": "6553a38f2c33e9939d0a2394",
"_tpl": "617153016c780c1e710c9a2f",
"parentId": "6553a38f2c33e9939d0a2391",
"slotId": "mod_stock"
}, {
"_id": "6553a38f2c33e9939d0a2395",
"_tpl": "617154aa1cb55961fa0fdb3b",
"parentId": "6553a38f2c33e9939d0a2394",
"slotId": "mod_stock_000"
}, {
"_id": "6553a38f2c33e9939d0a2396",
"_tpl": "61713a8fd92c473c770214a4",
"parentId": "6553a38f2c33e9939d0a2391",
"slotId": "mod_reciever"
}, {
"_id": "6553a38f2c33e9939d0a2397",
"_tpl": "6171407e50224f204c1da3c5",
"parentId": "6553a38f2c33e9939d0a2396",
"slotId": "mod_scope"
}, {
"_id": "6553a38f2c33e9939d0a2398",
"_tpl": "617151c1d92c473c770214ab",
"parentId": "6553a38f2c33e9939d0a2397",
"slotId": "mod_scope_000"
}, {
"_id": "6553a38f2c33e9939d0a2399",
"_tpl": "61702be9faa1272e431522c3",
"parentId": "6553a38f2c33e9939d0a2396",
"slotId": "mod_barrel"
}, {
"_id": "6553a38f2c33e9939d0a239a",
"_tpl": "61713308d92c473c770214a0",
"parentId": "6553a38f2c33e9939d0a2399",
"slotId": "mod_muzzle"
}, {
"_id": "6553a38f2c33e9939d0a239b",
"_tpl": "61702f1b67085e45ef140b26",
"parentId": "6553a38f2c33e9939d0a2399",
"slotId": "mod_gas_block"
}, {
"_id": "6553a38f2c33e9939d0a239c",
"_tpl": "61712eae6c780c1e710c9a1d",
"parentId": "6553a38f2c33e9939d0a2396",
"slotId": "mod_handguard"
}, {
"_id": "6553a38f2c33e9939d0a239d",
"_tpl": "5bb20e49d4351e3bac1212de",
"parentId": "6553a38f2c33e9939d0a2396",
"slotId": "mod_sight_rear"
}, {
"_id": "6553a38f2c33e9939d0a239e",
"_tpl": "61702d8a67085e45ef140b24",
"parentId": "6553a38f2c33e9939d0a2391",
"slotId": "mod_charge"
}, {
"_id": "6553a38f2c33e9939d0a239f",
"_tpl": "5447a9cd4bdc2dbd208b4567",
"upd": {
"FireMode": {
"FireMode": "single"
}
},
"parentId": "6553a38f2c33e9939d0a2390",
"slotId": "main"
}, {
"_id": "6553a38f2c33e9939d0a23a0",
"_tpl": "5cc9bcaed7f00c011c04e179",
"parentId": "6553a38f2c33e9939d0a239f",
"slotId": "mod_pistol_grip"
}, {
"_id": "6553a38f2c33e9939d0a23a1",
"_tpl": "59c1383d86f774290a37e0ca",
"parentId": "6553a38f2c33e9939d0a239f",
"slotId": "mod_magazine"
}, {
"_id": "6553a38f2c33e9939d0a23a2",
"_tpl": "5d4405aaa4b9361e6a4e6bd3",
"parentId": "6553a38f2c33e9939d0a239f",
"slotId": "mod_reciever"
}, {
"_id": "6553a38f2c33e9939d0a23a3",
"_tpl": "59f9d81586f7744c7506ee62",
"parentId": "6553a38f2c33e9939d0a23a2",
"slotId": "mod_scope"
}, {
"_id": "6553a38f2c33e9939d0a23a4",
"_tpl": "55d35ee94bdc2d61338b4568",
"parentId": "6553a38f2c33e9939d0a23a2",
"slotId": "mod_barrel"
}, {
"_id": "6553a38f2c33e9939d0a23a5",
"_tpl": "5cff9e5ed7ad1a09407397d4",
"parentId": "6553a38f2c33e9939d0a23a4",
"slotId": "mod_muzzle"
}, {
"_id": "6553a38f2c33e9939d0a23a6",
"_tpl": "5d00ec68d7ad1a04a067e5be",
"parentId": "6553a38f2c33e9939d0a23a4",
"slotId": "mod_gas_block"
}, {
"_id": "6553a38f2c33e9939d0a23a7",
"_tpl": "5c78f2612e221600114c9f0d",
"parentId": "6553a38f2c33e9939d0a23a2",
"slotId": "mod_handguard"
}, {
"_id": "6553a38f2c33e9939d0a23a8",
"_tpl": "5b7be4895acfc400170e2dd5",
"parentId": "6553a38f2c33e9939d0a23a7",
"slotId": "mod_foregrip"
}, {
"_id": "6553a38f2c33e9939d0a23a9",
"_tpl": "588226d124597767ad33f787",
"parentId": "6553a38f2c33e9939d0a23a8",
"slotId": "mod_foregrip"
}, {
"_id": "6553a38f2c33e9939d0a23aa",
"_tpl": "5ba26b01d4351e0085325a51",
"parentId": "6553a38f2c33e9939d0a23a7",
"slotId": "mod_sight_front"
}, {
"_id": "6553a38f2c33e9939d0a23ab",
"_tpl": "5c78f2882e22165df16b832e",
"parentId": "6553a38f2c33e9939d0a23a7",
"slotId": "mod_muzzle"
}, {
"_id": "6553a38f2c33e9939d0a23ac",
"_tpl": "5b07dd285acfc4001754240d",
"parentId": "6553a38f2c33e9939d0a23a7",
"slotId": "mod_tactical_002"
}, {
"_id": "6553a38f2c33e9939d0a23ad",
"_tpl": "5ba26b17d4351e00367f9bdd",
"parentId": "6553a38f2c33e9939d0a23a2",
"slotId": "mod_sight_rear"
}, {
"_id": "6553a38f2c33e9939d0a23ae",
"_tpl": "5d120a10d7ad1a4e1026ba85",
"parentId": "6553a38f2c33e9939d0a239f",
"slotId": "mod_stock"
}, {
"_id": "6553a38f2c33e9939d0a23af",
"_tpl": "5d120a28d7ad1a1c8962e295",
"parentId": "6553a38f2c33e9939d0a23ae",
"slotId": "mod_stock"
}, {
"_id": "6553a38f2c33e9939d0a23b0",
"_tpl": "5d44334ba4b9362b346d1948",
"parentId": "6553a38f2c33e9939d0a239f",
"slotId": "mod_charge"
}, {
"_id": "6553a38f2c33e9939d0a23b1",
"_tpl": "5c0e655586f774045612eeb2",
"parentId": "6553a38f2c33e9939d0a2390",
"slotId": "main"
}
],
"sender": "System",
"messageText": "IAMMIGHTY",
"collectionTimeHours": 48,
"associatedEvent": "Promo"
},
"ARMORPLATES": {
"items": [{
"_id": "6553aa39382bf255500f5a73",
"_tpl": "5c0e722886f7740458316a57",
"parentId": "6553aa39382bf255500f5a72",
"slotId": "main"
}, {
"_id": "6553aa39382bf255500f5a74",
"_tpl": "544a5caa4bdc2d1a388b4568",
"parentId": "6553aa39382bf255500f5a72",
"slotId": "main"
}, {
"_id": "6553aa39382bf255500f5a75",
"_tpl": "5b40e2bc5acfc40016388216",
"parentId": "6553aa39382bf255500f5a72",
"slotId": "main"
}, {
"_id": "6553aa39382bf255500f5a76",
"_tpl": "5b40e2bc5acfc40016388216",
"parentId": "6553aa39382bf255500f5a72",
"slotId": "main"
}
],
"sender": "System",
"messageText": "ARMORPLATES",
"collectionTimeHours": 48,
"associatedEvent": "Promo"
},
"RICHANDEXPENSIVE": {
"items": [{
"_id": "6553adb4e56992db30039456",
"_tpl": "5c0e530286f7747fa1419862",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}, {
"_id": "6553adb4e56992db30039457",
"_tpl": "5c0e530286f7747fa1419862",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}, {
"_id": "6553adb4e56992db30039458",
"_tpl": "5c0e533786f7747fa23f4d47",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}, {
"_id": "6553adb4e56992db30039459",
"_tpl": "5c0e533786f7747fa23f4d47",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}, {
"_id": "6553adb4e56992db3003945a",
"_tpl": "5ed5160a87bb8443d10680b5",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}, {
"_id": "6553adb4e56992db3003945b",
"_tpl": "5ed5160a87bb8443d10680b5",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}, {
"_id": "6553adb4e56992db3003945c",
"_tpl": "637b620db7afa97bfc3d7009",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}, {
"_id": "6553adb4e56992db3003945d",
"_tpl": "637b620db7afa97bfc3d7009",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}, {
"_id": "6553adb4e56992db3003945e",
"_tpl": "5c0e534186f7747fa1419867",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}, {
"_id": "6553adb4e56992db3003945f",
"_tpl": "5c0e534186f7747fa1419867",
"parentId": "6553adb4e56992db30039455",
"slotId": "main"
}
],
"sender": "System",
"messageText": "RICHANDEXPENSIVE",
"collectionTimeHours": 48,
"associatedEvent": "Promo"
},
"LEFTHANDHEADEYES": {
"items": [{
"_id": "6553aef5e83f97d5d30172d6",
"_tpl": "5d03775b86f774203e7e0c4b",
"parentId": "6553aef5e83f97d5d30172d5",
"slotId": "main"
}, {
"_id": "6553aef5e83f97d5d30172d7",
"_tpl": "5c12613b86f7743bbe2c3f76",
"parentId": "6553aef5e83f97d5d30172d5",
"slotId": "main"
}, {
"_id": "6553aef5e83f97d5d30172d8",
"_tpl": "5d1b36a186f7742523398433",
"parentId": "6553aef5e83f97d5d30172d5",
"slotId": "main"
}
],
"sender": "System",
"messageText": "LEFTHANDHEADEYES",
"collectionTimeHours": 48,
"associatedEvent": "Promo"
} }
} }
} }

View File

@ -27,6 +27,7 @@
"Exit_E10_coop" "Exit_E10_coop"
], ],
"carExtractBaseStandingGain": 0.4, "carExtractBaseStandingGain": 0.4,
"coopExtractBaseStandingGain": 0.25,
"scavExtractGain": 0.01, "scavExtractGain": 0.01,
"keepFiRSecureContainerOnDeath": false "keepFiRSecureContainerOnDeath": false
} }

View File

@ -144,8 +144,8 @@
"minPlayerLevel": 5, "minPlayerLevel": 5,
"rewardScaling": { "rewardScaling": {
"levels": [1, 10, 20, 30, 40, 50, 60], "levels": [1, 10, 20, 30, 40, 50, 60],
"experience": [1000, 5000, 11000, 59000, 147000, 250000, 450000], "experience": [1000, 2000, 8000, 14000, 20000, 24000, 28000],
"roubles": [15000, 40000, 75000, 100000, 140000, 170000, 210000], "roubles": [11000, 20000, 45000, 60000, 77000, 95000, 115000],
"items": [2, 4, 5, 5, 5, 5, 5], "items": [2, 4, 5, 5, 5, 5, 5],
"reputation": [0.01, 0.01, 0.02, 0.02, 0.03, 0.03, 0.03], "reputation": [0.01, 0.01, 0.02, 0.02, 0.03, 0.03, 0.03],
"rewardSpread": 0.5, "rewardSpread": 0.5,
@ -166,25 +166,98 @@
}, },
"traderWhitelist": [{ "traderWhitelist": [{
"traderId": "54cb50c76803fa8b248b4571", "traderId": "54cb50c76803fa8b248b4571",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "prapor",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"543be6564bdc2df4348b4568",
"5485a8684bdc2da71d8b4567",
"590c745b86f7743cc433c5f2",
"5422acb9af1c889c16000029"
]
}, { }, {
"traderId": "54cb57776803fa99248b456e", "traderId": "54cb57776803fa99248b456e",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "therapist",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"57864a66245977548f04a81f",
"5448f39d4bdc2d0a728b4568",
"5448f3ac4bdc2dce718b4569",
"5448f3a64bdc2d60728b456a",
"57864c322459775490116fbf",
"57864c8c245977548867e7f1",
"5448e8d04bdc2ddf718b4569",
"57864e4c24597754843f8723",
"57864ee62459775490116fc1"
]
}, { }, {
"traderId": "58330581ace78e27b8b10cee", "traderId": "58330581ace78e27b8b10cee",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "skier",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"5a341c4086f77401f2541505",
"5448e8d64bdc2dce718b4568",
"5448e8d04bdc2ddf718b4569",
"5422acb9af1c889c16000029",
"55818ad54bdc2ddc698b4569",
"57864a3d24597754843f8721",
"5a341c4686f77469e155819e",
"55818b224bdc2dde698b456f",
"5c99f98d86f7745c314214b3",
"55818aeb4bdc2ddc698b456a",
"55818acf4bdc2dde698b456b",
"57864bb7245977548b3b66c2",
"590c745b86f7743cc433c5f2"
]
}, { }, {
"traderId": "5935c25fb3acc3127c3d8cd9", "traderId": "5935c25fb3acc3127c3d8cd9",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "peacekeeper",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"5422acb9af1c889c16000029",
"543be6564bdc2df4348b4568",
"5448e5284bdc2dcb718b4567",
"5485a8684bdc2da71d8b4567",
"57864a3d24597754843f8721",
"55818af64bdc2d5b648b4570"
]
}, { }, {
"traderId": "5a7c2eca46aef81a7ca2145d", "traderId": "5a7c2eca46aef81a7ca2145d",
"questTypes": ["Completion", "Exploration"] "name": "mechanic",
"questTypes": ["Completion", "Exploration"],
"rewardBaseWhitelist": [
"55818af64bdc2d5b648b4570",
"5448bc234bdc2d3c308b4569",
"55818b164bdc2ddc698b456c",
"55818a684bdc2ddd698b456d",
"550aa4cd4bdc2dd8348b456c",
"5422acb9af1c889c16000029",
"5485a8684bdc2da71d8b4567",
"55818b224bdc2dde698b456f"
]
}, { }, {
"traderId": "5ac3b934156ae10c4430e83c", "traderId": "5ac3b934156ae10c4430e83c",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "ragman",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"5a341c4086f77401f2541505",
"5448e5724bdc2ddf718b4568",
"5448e54d4bdc2dcc718b4568",
"590c745b86f7743cc433c5f2",
"57bef4c42459772e8d35a53b",
"5448e53e4bdc2d60728b4567",
"5448e5284bdc2dcb718b4567",
"57864a66245977548f04a81f"
]
}, { }, {
"traderId": "5c0647fdd443bc2504c2d371", "traderId": "5c0647fdd443bc2504c2d371",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "jaeger",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"5448f3ac4bdc2dce718b4569",
"5c99f98d86f7745c314214b3",
"590c745b86f7743cc433c5f2",
"57864bb7245977548b3b66c2"
]
} }
], ],
"questConfig": { "questConfig": {
@ -692,7 +765,7 @@
} }
] ]
}, },
"rewardBaseTypeBlacklist": ["543be5e94bdc2df1348b4568", "5b3f15d486f77432d0509248"], "rewardBaseTypeBlacklist": ["543be5e94bdc2df1348b4568", "5b3f15d486f77432d0509248", "59f32c3b86f77472a31742f0", "59f32bb586f774757e1e8442"],
"rewardBlacklist": ["627bce33f21bc425b06ab967"], "rewardBlacklist": ["627bce33f21bc425b06ab967"],
"rewardAmmoStackMinSize": 20 "rewardAmmoStackMinSize": 20
}, { }, {
@ -731,25 +804,98 @@
}, },
"traderWhitelist": [{ "traderWhitelist": [{
"traderId": "54cb50c76803fa8b248b4571", "traderId": "54cb50c76803fa8b248b4571",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "prapor",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"543be6564bdc2df4348b4568",
"5485a8684bdc2da71d8b4567",
"590c745b86f7743cc433c5f2",
"5422acb9af1c889c16000029"
]
}, { }, {
"traderId": "54cb57776803fa99248b456e", "traderId": "54cb57776803fa99248b456e",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "therapist",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"57864a66245977548f04a81f",
"5448f39d4bdc2d0a728b4568",
"5448f3ac4bdc2dce718b4569",
"5448f3a64bdc2d60728b456a",
"57864c322459775490116fbf",
"57864c8c245977548867e7f1",
"5448e8d04bdc2ddf718b4569",
"57864e4c24597754843f8723",
"57864ee62459775490116fc1"
]
}, { }, {
"traderId": "58330581ace78e27b8b10cee", "traderId": "58330581ace78e27b8b10cee",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "skier",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"5a341c4086f77401f2541505",
"5448e8d64bdc2dce718b4568",
"5448e8d04bdc2ddf718b4569",
"5422acb9af1c889c16000029",
"55818ad54bdc2ddc698b4569",
"57864a3d24597754843f8721",
"5a341c4686f77469e155819e",
"55818b224bdc2dde698b456f",
"5c99f98d86f7745c314214b3",
"55818aeb4bdc2ddc698b456a",
"55818acf4bdc2dde698b456b",
"57864bb7245977548b3b66c2",
"590c745b86f7743cc433c5f2"
]
}, { }, {
"traderId": "5935c25fb3acc3127c3d8cd9", "traderId": "5935c25fb3acc3127c3d8cd9",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "peacekeeper",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"5422acb9af1c889c16000029",
"543be6564bdc2df4348b4568",
"5448e5284bdc2dcb718b4567",
"5485a8684bdc2da71d8b4567",
"57864a3d24597754843f8721",
"55818af64bdc2d5b648b4570"
]
}, { }, {
"traderId": "5a7c2eca46aef81a7ca2145d", "traderId": "5a7c2eca46aef81a7ca2145d",
"questTypes": ["Completion", "Exploration"] "name": "mechanic",
"questTypes": ["Completion", "Exploration"],
"rewardBaseWhitelist": [
"55818af64bdc2d5b648b4570",
"5448bc234bdc2d3c308b4569",
"55818b164bdc2ddc698b456c",
"55818a684bdc2ddd698b456d",
"550aa4cd4bdc2dd8348b456c",
"5422acb9af1c889c16000029",
"5485a8684bdc2da71d8b4567",
"55818b224bdc2dde698b456f"
]
}, { }, {
"traderId": "5ac3b934156ae10c4430e83c", "traderId": "5ac3b934156ae10c4430e83c",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "ragman",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"5a341c4086f77401f2541505",
"5448e5724bdc2ddf718b4568",
"5448e54d4bdc2dcc718b4568",
"590c745b86f7743cc433c5f2",
"57bef4c42459772e8d35a53b",
"5448e53e4bdc2d60728b4567",
"5448e5284bdc2dcb718b4567",
"57864a66245977548f04a81f"
]
}, { }, {
"traderId": "5c0647fdd443bc2504c2d371", "traderId": "5c0647fdd443bc2504c2d371",
"questTypes": ["Completion", "Exploration", "Elimination"] "name": "jaeger",
"questTypes": ["Completion", "Exploration", "Elimination"],
"rewardBaseWhitelist": [
"5448f3ac4bdc2dce718b4569",
"5c99f98d86f7745c314214b3",
"590c745b86f7743cc433c5f2",
"57864bb7245977548b3b66c2"
]
} }
], ],
"questConfig": { "questConfig": {
@ -1323,7 +1469,7 @@
} }
] ]
}, },
"rewardBaseTypeBlacklist": ["543be5e94bdc2df1348b4568", "5b3f15d486f77432d0509248"], "rewardBaseTypeBlacklist": ["543be5e94bdc2df1348b4568", "5b3f15d486f77432d0509248", "59f32c3b86f77472a31742f0", "59f32bb586f774757e1e8442"],
"rewardBlacklist": ["627bce33f21bc425b06ab967"], "rewardBlacklist": ["627bce33f21bc425b06ab967"],
"rewardAmmoStackMinSize": 15 "rewardAmmoStackMinSize": 15
}, { }, {
@ -1362,7 +1508,15 @@
}, },
"traderWhitelist": [{ "traderWhitelist": [{
"traderId": "579dc571d53a0658a154fbec", "traderId": "579dc571d53a0658a154fbec",
"questTypes": ["Completion", "Exploration", "Elimination", "Pickup"] "questTypes": ["Completion", "Exploration", "Elimination", "Pickup"],
"rewardBaseWhitelist": [
"55818a684bdc2ddd698b456d",
"55818a594bdc2db9688b456a",
"57864c8c245977548867e7f1",
"5448ecbe4bdc2d60728b4568",
"5422acb9af1c889c16000029",
"57864bb7245977548b3b66c2"
]
} }
], ],
"questConfig": { "questConfig": {

View File

@ -2117,7 +2117,7 @@
"weights": { "weights": {
"0": 1, "0": 1,
"1": 2, "1": 2,
"2": 0 "2": 2
}, },
"whitelist": [] "whitelist": []
}, },
@ -2136,7 +2136,7 @@
"weights": { "weights": {
"0": 1, "0": 1,
"1": 2, "1": 2,
"2": 1 "2": 3
}, },
"whitelist": [] "whitelist": []
}, },
@ -2162,16 +2162,16 @@
}, },
"specialItems": { "specialItems": {
"weights": { "weights": {
"0": 1, "0": 0,
"1": 0 "1": 1
}, },
"whitelist": [] "whitelist": []
}, },
"stims": { "stims": {
"weights": { "weights": {
"0": 2, "0": 1,
"1": 1, "1": 1,
"2": 0 "2": 2
}, },
"whitelist": [] "whitelist": []
}, },

View File

@ -2134,7 +2134,7 @@
"weights": { "weights": {
"0": 1, "0": 1,
"1": 2, "1": 2,
"2": 0 "2": 2
}, },
"whitelist": [] "whitelist": []
}, },
@ -2153,7 +2153,7 @@
"weights": { "weights": {
"0": 1, "0": 1,
"1": 2, "1": 2,
"2": 1 "2": 2
}, },
"whitelist": [] "whitelist": []
}, },
@ -2186,9 +2186,9 @@
}, },
"stims": { "stims": {
"weights": { "weights": {
"0": 2, "0": 1,
"1": 1, "1": 2,
"2": 0 "2": 1
}, },
"whitelist": [] "whitelist": []
}, },

View File

@ -181,6 +181,7 @@
"ragfair-offer_not_found_unable_to_hide": "hideItem() offerId: %s not found, unable to hide offer", "ragfair-offer_not_found_unable_to_hide": "hideItem() offerId: %s not found, unable to hide offer",
"ragfair-tpl_not_a_valid_item": "generateFleaOffersForTrader() tpl: %s not a valid item, skipping", "ragfair-tpl_not_a_valid_item": "generateFleaOffersForTrader() tpl: %s not a valid item, skipping",
"ragfair-unable_to_find_item_in_inventory": "Unable to find item with id: {{id}} in inventory", "ragfair-unable_to_find_item_in_inventory": "Unable to find item with id: {{id}} in inventory",
"ragfair-unable_to_find_offer_to_remove": "Unable to find offer with id: %s to remove",
"ragfair-unable_to_find_requested_items_in_inventory": "Unable to find any requested items in the inventory", "ragfair-unable_to_find_requested_items_in_inventory": "Unable to find any requested items in the inventory",
"ragfair-unable_to_pay_commission_fee": "Unable to pay commission fee: %s roubles", "ragfair-unable_to_pay_commission_fee": "Unable to pay commission fee: %s roubles",
"ragfair-offer_no_longer_exists": "Offer no longer exists", "ragfair-offer_no_longer_exists": "Offer no longer exists",
@ -592,8 +593,9 @@
"launcher-profile_standard": "Same as live, basic stash size (10x28), 500,000 roubles", "launcher-profile_standard": "Same as live, basic stash size (10x28), 500,000 roubles",
"launcher-profile_leftbehind": "Same as Standard plus; larger stash size (10x38), extra equipment/items, 500 dollars", "launcher-profile_leftbehind": "Same as Standard plus; larger stash size (10x38), extra equipment/items, 500 dollars",
"launcher-profile_preparetoescape": "Same as Left Behind plus; larger stash size (10x48), extra equipment/items, higher starting reputation with traders, 250 euros", "launcher-profile_preparetoescape": "Same as Left Behind plus; larger stash size (10x48), extra equipment/items, higher starting reputation with traders, 250 euros",
"launcher-edgeofdarkness": "Same as Prepare To Escape plus; larger stash size (10x68), extra equipment/items, higher starting reputation with traders, 1000 dollars, 500 euros", "launcher-profile-edgeofdarkness": "Same as Prepare To Escape plus; larger stash size (10x68), extra equipment/items, higher starting reputation with traders, 1000 dollars, 500 euros",
"launcher-profile_spteasystart": "Lots of Roubles/Dollars/Euros, Some QoL skills are level 20, trader rep maxed, starting level is 69, no quests completed", "launcher-profile_spteasystart": "Lots of Roubles/Dollars/Euros, Some QoL skills are level 20, trader rep maxed, starting level is 69, no quests completed",
"launcher-profile_sptzerotohero": "Start with almost nothing, no Roubles/Dollars/Euros, no trader rep, 1 knife, no quests completed", "launcher-profile_sptzerotohero": "Start with almost nothing, no Roubles/Dollars/Euros, no trader rep, 1 knife, no quests completed",
"launcher-profile_sptdeveloper": "Testing profile, starting level is 69, lots of Roubles/Dollars/Euros, USEC start with all quests ready to start, BEAR start with all quests ready to hand in, invincibility balaclava" "launcher-profile_sptdeveloper": "Testing profile, starting level is 69, lots of Roubles/Dollars/Euros, USEC start with all quests ready to start, BEAR start with all quests ready to hand in, invincibility balaclava",
"launcher-missing_property": "Profile: %s is missing a descriptionLocaleKey property"
} }

View File

@ -402,7 +402,7 @@
"launcher-profile_standard": "Igual que en multijugador, el tamaño básico del escondite (10x28), 500.000 rublos", "launcher-profile_standard": "Igual que en multijugador, el tamaño básico del escondite (10x28), 500.000 rublos",
"launcher-profile_leftbehind": "Igual que Standard plus; mayor tamaño de inventario(10x38), equipamiento extra, 500 dólares", "launcher-profile_leftbehind": "Igual que Standard plus; mayor tamaño de inventario(10x38), equipamiento extra, 500 dólares",
"launcher-profile_preparetoescape": "Igual que Left Behind plus; mayor tamaño de inventario (10x48), equipamiento adicional/artículos, mayor reputación inicial con los comerciantes, 250 euros", "launcher-profile_preparetoescape": "Igual que Left Behind plus; mayor tamaño de inventario (10x48), equipamiento adicional/artículos, mayor reputación inicial con los comerciantes, 250 euros",
"launcher-edgeofdarkness": "Igual que Prepare to Escape plus; mayor tamaño de escondite (10x68), equipo adicional/artículos, mayor reputación inicial con los comerciantes, 1000 dólares, 500 euros", "launcher-profile-edgeofdarkness": "Igual que Prepare to Escape plus; mayor tamaño de escondite (10x68), equipo adicional/artículos, mayor reputación inicial con los comerciantes, 1000 dólares, 500 euros",
"launcher-profile_spteasystart": "Muchos rublos/dólares/euros, algunas habilidades de QoL son de nivel 20, la reputación de los comerciantes al máximo, el nivel inicial es 69, no hay misiones completadas", "launcher-profile_spteasystart": "Muchos rublos/dólares/euros, algunas habilidades de QoL son de nivel 20, la reputación de los comerciantes al máximo, el nivel inicial es 69, no hay misiones completadas",
"launcher-profile_sptzerotohero": "Comienza con casi nada, sin rublos/dólares/euros, sin reputación de comerciantes, 1 cuchillo y sin misiones completadas", "launcher-profile_sptzerotohero": "Comienza con casi nada, sin rublos/dólares/euros, sin reputación de comerciantes, 1 cuchillo y sin misiones completadas",
"launcher-profile_sptdeveloper": "Prefil de prueba, el nivel inicial es 69, muchos rublos/dólares/euros, USEC comienza con todas las misiones listas para empezar, BEAR comienza con todas las misiones listas para entregar, pasamontañas de invencibilidad" "launcher-profile_sptdeveloper": "Prefil de prueba, el nivel inicial es 69, muchos rublos/dólares/euros, USEC comienza con todas las misiones listas para empezar, BEAR comienza con todas las misiones listas para entregar, pasamontañas de invencibilidad"

View File

@ -553,7 +553,7 @@
"launcher-profile_standard": "Edition Standard, stash basique (10x28), 500 000 roubles", "launcher-profile_standard": "Edition Standard, stash basique (10x28), 500 000 roubles",
"launcher-profile_leftbehind": "Edition Left Behind, stash moyenne (10x38), équipement en plus, 500 dollars", "launcher-profile_leftbehind": "Edition Left Behind, stash moyenne (10x38), équipement en plus, 500 dollars",
"launcher-profile_preparetoescape": "Edition Prepare for Escape, stash grande (10x48), plus d'équipement, meilleure rép de démarrage vendeurs, 250 euros", "launcher-profile_preparetoescape": "Edition Prepare for Escape, stash grande (10x48), plus d'équipement, meilleure rép de démarrage vendeurs, 250 euros",
"launcher-edgeofdarkness": "Edition EoD, stash de taille maximale (10x68), un max d'équipement, gros boost avec la rép des traders, 1000 dollars, 500 euros", "launcher-profile-edgeofdarkness": "Edition EoD, stash de taille maximale (10x68), un max d'équipement, gros boost avec la rép des traders, 1000 dollars, 500 euros",
"launcher-profile_spteasystart": "SPT mode touriste. Beaucoup de Roubles/Dollars/Euros, certains skills confort sont à 20, réputation traders maximale, level de départ à 69. Aucune quête validée", "launcher-profile_spteasystart": "SPT mode touriste. Beaucoup de Roubles/Dollars/Euros, certains skills confort sont à 20, réputation traders maximale, level de départ à 69. Aucune quête validée",
"launcher-profile_sptzerotohero": "SPT zero to hero ! Tu démarres avec rien, juste ta bite et ton couteau ! Aucune quête validée", "launcher-profile_sptzerotohero": "SPT zero to hero ! Tu démarres avec rien, juste ta bite et ton couteau ! Aucune quête validée",
"launcher-profile_sptdeveloper": "SPT dév/testing/debug, lvl 69, max Roubles/Dollars/Euros, USEC ==> toutes les quêtes prêtes à être débutées, BEAR ==> toutes les quêtes terminées prêtes à être validées, item balaclava d'invincibilité" "launcher-profile_sptdeveloper": "SPT dév/testing/debug, lvl 69, max Roubles/Dollars/Euros, USEC ==> toutes les quêtes prêtes à être débutées, BEAR ==> toutes les quêtes terminées prêtes à être validées, item balaclava d'invincibilité"

View File

@ -208,7 +208,7 @@
"launcher-profile_standard": "Uguale al profilo standard di live EFT, dimensioni standard del nascondiglio (10x28), 500,000 rubli", "launcher-profile_standard": "Uguale al profilo standard di live EFT, dimensioni standard del nascondiglio (10x28), 500,000 rubli",
"launcher-profile_leftbehind": "Uguale al profilo standard di live EFT ed in più; dimensioni del nascondiglio aumentato (10x38), oggetti/equipaggiamento extra, 500 dollari", "launcher-profile_leftbehind": "Uguale al profilo standard di live EFT ed in più; dimensioni del nascondiglio aumentato (10x38), oggetti/equipaggiamento extra, 500 dollari",
"launcher-profile_preparetoescape": "Uguale al profilo Left Behind di live EFT ed in più; dimensioni del nascondiglio aumentato (10x48), oggetti/equipaggiamento extra, reputazione con i mercanti più alta, 250 euro", "launcher-profile_preparetoescape": "Uguale al profilo Left Behind di live EFT ed in più; dimensioni del nascondiglio aumentato (10x48), oggetti/equipaggiamento extra, reputazione con i mercanti più alta, 250 euro",
"launcher-edgeofdarkness": "Uguale al profilo Prepare to Escape di live EFT ed in più; dimensioni del nascondiglio massime (10x68), oggetti/equipaggiamento extra, reputazione con i mercanti più alta, 1000 dollari, 500 euro", "launcher-profile-edgeofdarkness": "Uguale al profilo Prepare to Escape di live EFT ed in più; dimensioni del nascondiglio massime (10x68), oggetti/equipaggiamento extra, reputazione con i mercanti più alta, 1000 dollari, 500 euro",
"launcher-profile_spteasystart": "Gran numero di Rubli/Dollari/Euro, alcune abilità utili sono già livello 20, reputazione con i mercanti al massimo, i PMC iniziano al livello 69, nessuna missione completata", "launcher-profile_spteasystart": "Gran numero di Rubli/Dollari/Euro, alcune abilità utili sono già livello 20, reputazione con i mercanti al massimo, i PMC iniziano al livello 69, nessuna missione completata",
"launcher-profile_sptzerotohero": "Inizi con quasi nulla, niente Rubli/Dollari/Euro, reputazione mercanti a zero, un coltello, nessuna missione completata", "launcher-profile_sptzerotohero": "Inizi con quasi nulla, niente Rubli/Dollari/Euro, reputazione mercanti a zero, un coltello, nessuna missione completata",
"launcher-profile_sptdeveloper": "Profile per testare, i PMC iniziano al livello 69, molti Rubli/Dollari/Euro, USEC cominciano con tutte le quest pronte a cominciare, BEAR comincia con tutte le quest pronte ad essere consegnate, balaclava dell'invicibilità incluso", "launcher-profile_sptdeveloper": "Profile per testare, i PMC iniziano al livello 69, molti Rubli/Dollari/Euro, USEC cominciano con tutte le quest pronte a cominciare, BEAR comincia con tutte le quest pronte ad essere consegnate, balaclava dell'invicibilità incluso",

View File

@ -479,7 +479,7 @@
"launcher-profile_standard": "라이브와 동일, 기본 보관함 크기(10x28), 500,000루블", "launcher-profile_standard": "라이브와 동일, 기본 보관함 크기(10x28), 500,000루블",
"launcher-profile_leftbehind": "스탠다드 + 더 큰 보관함 크기(10x38), 추가 장비/아이템, 500달러", "launcher-profile_leftbehind": "스탠다드 + 더 큰 보관함 크기(10x38), 추가 장비/아이템, 500달러",
"launcher-profile_preparetoescape": "레프트 비하인드와 동일 더 큰 보관함 크기(10x48), 추가 장비/아이템, 상인과의 시작 평판 상승, 250유로", "launcher-profile_preparetoescape": "레프트 비하인드와 동일 더 큰 보관함 크기(10x48), 추가 장비/아이템, 상인과의 시작 평판 상승, 250유로",
"launcher-edgeofdarkness": "프리페어 투 이스케이프와 동일 더 큰 은신처 크기(10x68), 추가 장비/아이템, 상인과의 높은 시작 평판, 1000달러, 500유로", "launcher-profile-edgeofdarkness": "프리페어 투 이스케이프와 동일 더 큰 은신처 크기(10x68), 추가 장비/아이템, 상인과의 높은 시작 평판, 1000달러, 500유로",
"launcher-profile_spteasystart": "많은 루블/달러/유로, 일부 품질 관리 기술 레벨 20, 상인 담당자 최대 레벨, 시작 레벨 69, 완료된 퀘스트 없음", "launcher-profile_spteasystart": "많은 루블/달러/유로, 일부 품질 관리 기술 레벨 20, 상인 담당자 최대 레벨, 시작 레벨 69, 완료된 퀘스트 없음",
"launcher-profile_sptzerotohero": "루블/달러/유로, 상인 대표, 칼 1개, 퀘스트 완료 등 거의 아무것도 없이 시작하세요.", "launcher-profile_sptzerotohero": "루블/달러/유로, 상인 대표, 칼 1개, 퀘스트 완료 등 거의 아무것도 없이 시작하세요.",
"launcher-profile_sptdeveloper": "테스트 프로필, 시작 레벨은 69, 많은 루블/달러/유로, USEC는 모든 퀘스트를 시작할 준비가 된 상태로 시작, BEAR는 모든 퀘스트를 제출할 준비가 된 상태로 시작, 무적 발라 클라바" "launcher-profile_sptdeveloper": "테스트 프로필, 시작 레벨은 69, 많은 루블/달러/유로, USEC는 모든 퀘스트를 시작할 준비가 된 상태로 시작, BEAR는 모든 퀘스트를 제출할 준비가 된 상태로 시작, 무적 발라 클라바"

View File

@ -553,7 +553,7 @@
"launcher-profile_standard": "Zelfde als live, basis stash grootte (10x28), 500,000 roubles", "launcher-profile_standard": "Zelfde als live, basis stash grootte (10x28), 500,000 roubles",
"launcher-profile_leftbehind": "Zelfde als Standard plus; grotere stash (10x38), extra uitrusting/items, 500 dollars", "launcher-profile_leftbehind": "Zelfde als Standard plus; grotere stash (10x38), extra uitrusting/items, 500 dollars",
"launcher-profile_preparetoescape": "Zelfde als Left Behind plus; grotere stash (10x48), extra uitrusting/items, hogere start reputatie met traders, 250 euros", "launcher-profile_preparetoescape": "Zelfde als Left Behind plus; grotere stash (10x48), extra uitrusting/items, hogere start reputatie met traders, 250 euros",
"launcher-edgeofdarkness": "Zelfde als Prepare To Escape plus; grotere stash (10x68), extra uitrusting/items, hogere start reputatie met traders, 1000 dollars, 500 euros", "launcher-profile-edgeofdarkness": "Zelfde als Prepare To Escape plus; grotere stash (10x68), extra uitrusting/items, hogere start reputatie met traders, 1000 dollars, 500 euros",
"launcher-profile_spteasystart": "Heel veel Roubles/Dollars/Euros, Een aantal QoL skills zijn level 20, trader rep maximaal, start level is 69, geen voltooide missies", "launcher-profile_spteasystart": "Heel veel Roubles/Dollars/Euros, Een aantal QoL skills zijn level 20, trader rep maximaal, start level is 69, geen voltooide missies",
"launcher-profile_sptzerotohero": "Start met bijna niks, geen Roubles/Dollars/Euros, geen trader rep, 1 mes, geen voltooide missies", "launcher-profile_sptzerotohero": "Start met bijna niks, geen Roubles/Dollars/Euros, geen trader rep, 1 mes, geen voltooide missies",
"launcher-profile_sptdeveloper": "Test profiel, start level is 69, heel veel Roubles/Dollars/Euros, USEC start met alle missied klaar om te starten, BEAR start met alle missies klaar om ingeleverd te vorden, invincibility balaclava" "launcher-profile_sptdeveloper": "Test profiel, start level is 69, heel veel Roubles/Dollars/Euros, USEC start met alle missied klaar om te starten, BEAR start met alle missies klaar om ingeleverd te vorden, invincibility balaclava"

View File

@ -553,7 +553,7 @@
"launcher-profile_standard": "Tak samo jak na oficjalnym, podstawowy rozmiar schowka (10x28), 500 000 rubli", "launcher-profile_standard": "Tak samo jak na oficjalnym, podstawowy rozmiar schowka (10x28), 500 000 rubli",
"launcher-profile_leftbehind": "Tak samo jak Standard plus; większy rozmiar schowka (10x38), dodatkowy sprzęt/przedmioty, 500 dolarów", "launcher-profile_leftbehind": "Tak samo jak Standard plus; większy rozmiar schowka (10x38), dodatkowy sprzęt/przedmioty, 500 dolarów",
"launcher-profile_preparetoescape": "Tak samo jak Left Behind plus; większy rozmiar schowka (10x48), dodatkowy sprzęt/przedmioty, wyższa startowa reputacja u handlarzy, 250 euro", "launcher-profile_preparetoescape": "Tak samo jak Left Behind plus; większy rozmiar schowka (10x48), dodatkowy sprzęt/przedmioty, wyższa startowa reputacja u handlarzy, 250 euro",
"launcher-edgeofdarkness": "Tak samo jak Prepare To Escape plus; większy rozmiar schowka (10x68), dodatkowy sprzęt/przedmioty, wyższa startowa reputacja u handlarzy, 1000 dolarów, 500 euro", "launcher-profile-edgeofdarkness": "Tak samo jak Prepare To Escape plus; większy rozmiar schowka (10x68), dodatkowy sprzęt/przedmioty, wyższa startowa reputacja u handlarzy, 1000 dolarów, 500 euro",
"launcher-profile_spteasystart": "Dużo rubli/dolarów/euro, niektóre pomocne umiejętności na poziomie 20, maksymalna reputacja u handlarzy, początkowy poziom to 69, żadne zadania nie zostały ukończone", "launcher-profile_spteasystart": "Dużo rubli/dolarów/euro, niektóre pomocne umiejętności na poziomie 20, maksymalna reputacja u handlarzy, początkowy poziom to 69, żadne zadania nie zostały ukończone",
"launcher-profile_sptzerotohero": "Zaczynaj praktycznie od zera, bez rubli/dolarów/euro, bez reputacji u handlarzy, 1 nóż, żadne zadania nie zostały ukończone", "launcher-profile_sptzerotohero": "Zaczynaj praktycznie od zera, bez rubli/dolarów/euro, bez reputacji u handlarzy, 1 nóż, żadne zadania nie zostały ukończone",
"launcher-profile_sptdeveloper": "Profil testowy, początkowy poziom to 69, dużo rubli/dolarów/euro, USEC zaczyna z wszystkimi zadaniami gotowymi do rozpoczęcia, BEAR zaczyna z wszystkimi zadaniami gotowymi do oddania, kominiarka nieśmiertelności" "launcher-profile_sptdeveloper": "Profil testowy, początkowy poziom to 69, dużo rubli/dolarów/euro, USEC zaczyna z wszystkimi zadaniami gotowymi do rozpoczęcia, BEAR zaczyna z wszystkimi zadaniami gotowymi do oddania, kominiarka nieśmiertelności"

View File

@ -548,7 +548,7 @@
"launcher-profile_standard": "Вариант Standard, базовый размер схрона (10x28), 500,000 рублей", "launcher-profile_standard": "Вариант Standard, базовый размер схрона (10x28), 500,000 рублей",
"launcher-profile_leftbehind": "Вариант Left Behind; увеличенный размер схрона (10x38), дополнительное снаряжение/предметы, 500 долларов", "launcher-profile_leftbehind": "Вариант Left Behind; увеличенный размер схрона (10x38), дополнительное снаряжение/предметы, 500 долларов",
"launcher-profile_preparetoescape": "Вариант Prepare for Escape; увеличенный размер схрона (10x48), дополнительное снаряжение/предметы, изначально повышенная репутация со всеми торговцами в игре, 250 евро", "launcher-profile_preparetoescape": "Вариант Prepare for Escape; увеличенный размер схрона (10x48), дополнительное снаряжение/предметы, изначально повышенная репутация со всеми торговцами в игре, 250 евро",
"launcher-edgeofdarkness": "Вариант Edge of Darkness; увеличенный размер схрона (10x68), дополнительное снаряжение/предметы, изначально повышенная репутация со всеми торговцами в игре, 1000 долларов, 500 евро", "launcher-profile-edgeofdarkness": "Вариант Edge of Darkness; увеличенный размер схрона (10x68), дополнительное снаряжение/предметы, изначально повышенная репутация со всеми торговцами в игре, 1000 долларов, 500 евро",
"launcher-profile_spteasystart": "Много рублей/долларов/евро, некоторые QoL навыки 20 уровня, максимальная репа у троговцев, начальный уровень 69, задания не выполнены", "launcher-profile_spteasystart": "Много рублей/долларов/евро, некоторые QoL навыки 20 уровня, максимальная репа у троговцев, начальный уровень 69, задания не выполнены",
"launcher-profile_sptzerotohero": "Старт практические без всего, нет рублей/долларов/евро, нет репутации у торговцев, 1 нож, задания не выполнены", "launcher-profile_sptzerotohero": "Старт практические без всего, нет рублей/долларов/евро, нет репутации у торговцев, 1 нож, задания не выполнены",
"launcher-profile_sptdeveloper": "Профиль для тестирования, начальный уровень 69, много рублей/долларов/евро, USEC начинают со всеми заданиями, готовыми к принятию, BEAR начинают со всеми заданиями, готовыми к сдаче, балаклава неуязвимости" "launcher-profile_sptdeveloper": "Профиль для тестирования, начальный уровень 69, много рублей/долларов/евро, USEC начинают со всеми заданиями, готовыми к принятию, BEAR начинают со всеми заданиями, готовыми к сдаче, балаклава неуязвимости"

View File

@ -553,7 +553,7 @@
"launcher-profile_standard": "与在线相同基本储物空间10×28500,000卢布", "launcher-profile_standard": "与在线相同基本储物空间10×28500,000卢布",
"launcher-profile_leftbehind": "包含标准版内容以及;更大的储物空间;10×38额外的装备和物品500美元", "launcher-profile_leftbehind": "包含标准版内容以及;更大的储物空间;10×38额外的装备和物品500美元",
"launcher-profile_preparetoescape": "包含落后版内容以及更大的储物空间10×48额外的装备和物品更高的商人初始声望250欧元", "launcher-profile_preparetoescape": "包含落后版内容以及更大的储物空间10×48额外的装备和物品更高的商人初始声望250欧元",
"launcher-edgeofdarkness": "包含准备逃离版内容以及更大的储物空间10×68额外的装备和物品更高的商人初始声望1000美元500欧元", "launcher-profile-edgeofdarkness": "包含准备逃离版内容以及更大的储物空间10×68额外的装备和物品更高的商人初始声望1000美元500欧元",
"launcher-profile_spteasystart": "大量卢布、美元和欧元一些基础技能等级20商人声望满初始等级69没有已完成任务", "launcher-profile_spteasystart": "大量卢布、美元和欧元一些基础技能等级20商人声望满初始等级69没有已完成任务",
"launcher-profile_sptzerotohero": "开局几乎没有物资,没有卢布、美元或欧元,没有商人声望,就一把刀,没有已完成任务", "launcher-profile_sptzerotohero": "开局几乎没有物资,没有卢布、美元或欧元,没有商人声望,就一把刀,没有已完成任务",
"launcher-profile_sptdeveloper": "测试用存档初始等级69大量卢布、美元和欧元USEC开局所有的任务已准备开始BEAR开局所有任务已准备接取无敌面罩" "launcher-profile_sptdeveloper": "测试用存档初始等级69大量卢布、美元和欧元USEC开局所有的任务已准备开始BEAR开局所有任务已准备接取无敌面罩"

View File

@ -131,7 +131,7 @@
"BossEscortType": "followerBully", "BossEscortType": "followerBully",
"BossName": "bossBully", "BossName": "bossBully",
"BossPlayer": false, "BossPlayer": false,
"BossZone": "ZoneDormitory", "BossZone": "ZoneDormitory,ZoneGasStation,ZoneScavBase",
"Delay": 0, "Delay": 0,
"ForceSpawn": false, "ForceSpawn": false,
"IgnoreMaxBots": false, "IgnoreMaxBots": false,

View File

@ -49,7 +49,7 @@
"BossEscortType": "followerTagilla", "BossEscortType": "followerTagilla",
"BossName": "bossKilla", "BossName": "bossKilla",
"BossPlayer": false, "BossPlayer": false,
"BossZone": "ZoneCenter", "BossZone": "ZoneCenterBot,ZoneCenter,ZoneOLI,ZoneIDEA,ZoneGoshan",
"Delay": 0, "Delay": 0,
"ForceSpawn": false, "ForceSpawn": false,
"IgnoreMaxBots": false, "IgnoreMaxBots": false,

View File

@ -49,7 +49,7 @@
"BossEscortType": "followerGluharAssault", "BossEscortType": "followerGluharAssault",
"BossName": "bossGluhar", "BossName": "bossGluhar",
"BossPlayer": false, "BossPlayer": false,
"BossZone": "ZoneRailStrorage", "BossZone": "ZoneRailStrorage,ZoneRailStrorage,ZoneRailStrorage,ZonePTOR1,ZonePTOR2,ZoneBarrack,ZoneBarrack,ZoneBarrack,ZoneSubStorage",
"Delay": 0, "Delay": 0,
"ForceSpawn": false, "ForceSpawn": false,
"IgnoreMaxBots": false, "IgnoreMaxBots": false,

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "aki-server", "name": "aki-server",
"version": "3.7.1", "version": "3.8.0",
"author": "SPT-AKI Server", "author": "SPT-AKI Server",
"license": "NCSA", "license": "NCSA",
"main": "obj/bundle.js", "main": "obj/bundle.js",
@ -49,7 +49,6 @@
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.3.3", "@biomejs/biome": "1.3.3",
"@jest/globals": "29.7.0",
"@pnpm/exe": "8.9.0", "@pnpm/exe": "8.9.0",
"@swc/cli": "0.1.62", "@swc/cli": "0.1.62",
"@swc/core": "1.3.92", "@swc/core": "1.3.92",

View File

@ -490,13 +490,7 @@ export class HideoutController
const itemToReturn = hideoutArea.slots.find((x) => x.locationIndex === slotIndexToRemove).item[0]; const itemToReturn = hideoutArea.slots.find((x) => x.locationIndex === slotIndexToRemove).item[0];
const newReq = { const newReq = { items: [{ item_id: itemToReturn._tpl, count: 1 }], tid: "ragfair" };
items: [{
item_id: itemToReturn._tpl,
count: 1,
}],
tid: "ragfair",
};
output = this.inventoryHelper.addItem( output = this.inventoryHelper.addItem(
pmcData, pmcData,
@ -821,7 +815,7 @@ export class HideoutController
} }
// check if the recipe is the same as the last one // check if the recipe is the same as the last one
const area = pmcData.Hideout.Areas[recipe.areaType]; const area = pmcData.Hideout.Areas.find((x) => x.type === recipe.areaType);
if (area && request.recipeId !== area.lastRecipe) if (area && request.recipeId !== area.lastRecipe)
{ {
// 1 point per craft upon the end of production for alternating between 2 different crafting recipes in the same module // 1 point per craft upon the end of production for alternating between 2 different crafting recipes in the same module
@ -866,7 +860,7 @@ export class HideoutController
counterHoursCrafting.value = hoursCrafting; counterHoursCrafting.value = hoursCrafting;
// Null production data now it's complete - will be cleaned up later by update() process // Null production data now it's complete - will be cleaned up later by update() process
pmcData.Hideout.Production[prodId] = null; pmcData.Hideout.Production[prodId].sptIsComplete = true;
}; };
// Remove the old production from output object before its sent to client // Remove the old production from output object before its sent to client
@ -950,8 +944,8 @@ export class HideoutController
const callback = () => const callback = () =>
{ {
// Null production data now it's complete - will be cleaned up later by update() process // Flag as complete - will be cleaned up later by update() process
pmcData.Hideout.Production[prodId] = null; pmcData.Hideout.Production[prodId].sptIsComplete = true;
}; };
return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, true); return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, true);

View File

@ -32,6 +32,7 @@ import { TimeUtil } from "@spt-aki/utils/TimeUtil";
export class InsuranceController export class InsuranceController
{ {
protected insuranceConfig: IInsuranceConfig; protected insuranceConfig: IInsuranceConfig;
protected roubleTpl = "5449016a4bdc2d6f028b456f";
constructor( constructor(
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@ -590,17 +591,17 @@ export class InsuranceController
const itemsToInsureCount = body.items.length; const itemsToInsureCount = body.items.length;
const itemsToPay = []; const itemsToPay = [];
const inventoryItemsHash = {}; const inventoryItemsHash = {};
// Create hash of player inventory items (keyed by item id)
for (const item of pmcData.Inventory.items) for (const item of pmcData.Inventory.items)
{ {
inventoryItemsHash[item._id] = item; inventoryItemsHash[item._id] = item;
} }
// get the price of all items // Get price of all items being insured
for (const key of body.items) for (const key of body.items)
{ {
itemsToPay.push({ itemsToPay.push({
id: inventoryItemsHash[key]._id, id: this.roubleTpl, // TODO: update to handle different currencies
count: Math.round(this.insuranceService.getPremium(pmcData, inventoryItemsHash[key], body.tid)), count: Math.round(this.insuranceService.getPremium(pmcData, inventoryItemsHash[key], body.tid)),
}); });
} }
@ -608,7 +609,7 @@ export class InsuranceController
const options: IProcessBuyTradeRequestData = { const options: IProcessBuyTradeRequestData = {
scheme_items: itemsToPay, scheme_items: itemsToPay,
tid: body.tid, tid: body.tid,
Action: "", Action: "SptInsure",
type: "", type: "",
item_id: "", item_id: "",
count: 0, count: 0,

View File

@ -11,6 +11,7 @@ import { IConnectResponse } from "@spt-aki/models/eft/profile/IConnectResponse";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { ICoreConfig } from "@spt-aki/models/spt/config/ICoreConfig"; import { ICoreConfig } from "@spt-aki/models/spt/config/ICoreConfig";
import { IPackageJsonData } from "@spt-aki/models/spt/mod/IPackageJsonData"; import { IPackageJsonData } from "@spt-aki/models/spt/mod/IPackageJsonData";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { SaveServer } from "@spt-aki/servers/SaveServer"; import { SaveServer } from "@spt-aki/servers/SaveServer";
@ -23,6 +24,7 @@ export class LauncherController
protected coreConfig: ICoreConfig; protected coreConfig: ICoreConfig;
constructor( constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("HashUtil") protected hashUtil: HashUtil, @inject("HashUtil") protected hashUtil: HashUtil,
@inject("SaveServer") protected saveServer: SaveServer, @inject("SaveServer") protected saveServer: SaveServer,
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper, @inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
@ -47,22 +49,26 @@ export class LauncherController
} }
/** /**
* Get descriptive text for each of the profile editions a player can choose * Get descriptive text for each of the profile edtions a player can choose, keyed by profile.json profile type e.g. "Edge Of Darkness"
* @returns * @returns Dictionary of profile types with related descriptive text
*/ */
protected getProfileDescriptions(): Record<string, string> protected getProfileDescriptions(): Record<string, string>
{ {
return { const result = {};
/* eslint-disable @typescript-eslint/naming-convention */ const dbProfiles = this.databaseServer.getTables().templates.profiles;
Standard: this.localisationService.getText("launcher-profile_standard"), for (const profileKey in dbProfiles)
"Left Behind": this.localisationService.getText("launcher-profile_leftbehind"), {
"Prepare To Escape": this.localisationService.getText("launcher-profile_preparetoescape"), const localeKey = dbProfiles[profileKey]?.descriptionLocaleKey;
"Edge Of Darkness": this.localisationService.getText("launcher-edgeofdarkness"), if (!localeKey)
"SPT Easy start": this.localisationService.getText("launcher-profile_spteasystart"), {
"SPT Zero to hero": this.localisationService.getText("launcher-profile_sptzerotohero"), this.logger.warning(this.localisationService.getText("launcher-missing_property", profileKey));
"SPT Developer": this.localisationService.getText("launcher-profile_sptdeveloper"), continue;
/* eslint-enable @typescript-eslint/naming-convention */ }
};
result[profileKey] = this.localisationService.getText(localeKey);
}
return result;
} }
public find(sessionIdKey: string): Info public find(sessionIdKey: string): Info

View File

@ -191,6 +191,7 @@ export class MatchController
if (extractName && this.extractWasViaCoop(extractName) && this.traderConfig.fence.coopExtractGift.sendGift) if (extractName && this.extractWasViaCoop(extractName) && this.traderConfig.fence.coopExtractGift.sendGift)
{ {
this.handleCoopExtract(pmcData, extractName);
this.sendCoopTakenFenceMessage(sessionId); this.sendCoopTakenFenceMessage(sessionId);
} }
} }
@ -240,6 +241,41 @@ export class MatchController
); );
} }
/**
* Handle when a player extracts using a coop extract - add rep to fence
* @param pmcData Profile
* @param extractName Name of extract taken
*/
protected handleCoopExtract(pmcData: IPmcData, extractName: string): void
{
if (!pmcData.CoopExtractCounts)
{
pmcData.CoopExtractCounts = {};
}
// Ensure key exists for extract
if (!(extractName in pmcData.CoopExtractCounts))
{
pmcData.CoopExtractCounts[extractName] = 0;
}
// Increment extract count value
pmcData.CoopExtractCounts[extractName] += 1;
// Get new fence standing value
const newFenceStanding = this.getFenceStandingAfterExtract(
pmcData,
this.inraidConfig.coopExtractBaseStandingGain,
pmcData.CoopExtractCounts[extractName],
);
const fenceId: string = Traders.FENCE;
pmcData.TradersInfo[fenceId].standing = newFenceStanding;
// Check if new standing has leveled up trader
this.traderHelper.lvlUp(fenceId, pmcData);
pmcData.TradersInfo[fenceId].loyaltyLevel = Math.max(pmcData.TradersInfo[fenceId].loyaltyLevel, 1);
}
/** /**
* Was extract by car * Was extract by car
* @param extractName name of extract * @param extractName name of extract
@ -278,9 +314,17 @@ export class MatchController
// Increment extract count value // Increment extract count value
pmcData.CarExtractCounts[extractName] += 1; pmcData.CarExtractCounts[extractName] += 1;
// Not exact replica of Live behaviour
// Simplified for now, no real reason to do the whole (unconfirmed) extra 0.01 standing per day regeneration mechanic
const newFenceStanding = this.getFenceStandingAfterExtract(
pmcData,
this.inraidConfig.carExtractBaseStandingGain,
pmcData.CarExtractCounts[extractName],
);
const fenceId: string = Traders.FENCE; const fenceId: string = Traders.FENCE;
this.updateFenceStandingInProfile(pmcData, fenceId, extractName); pmcData.TradersInfo[fenceId].standing = newFenceStanding;
// Check if new standing has leveled up trader
this.traderHelper.lvlUp(fenceId, pmcData); this.traderHelper.lvlUp(fenceId, pmcData);
pmcData.TradersInfo[fenceId].loyaltyLevel = Math.max(pmcData.TradersInfo[fenceId].loyaltyLevel, 1); pmcData.TradersInfo[fenceId].loyaltyLevel = Math.max(pmcData.TradersInfo[fenceId].loyaltyLevel, 1);
@ -290,25 +334,25 @@ export class MatchController
} }
/** /**
* Update players fence trader standing value in profile * Get the fence rep gain from using a car or coop extract
* @param pmcData Player profile * @param pmcData Profile
* @param fenceId Id of fence trader * @param baseGain amount gained for the first extract
* @param extractName Name of extract used * @param extractCount Number of times extract was taken
* @returns Fence standing after taking extract
*/ */
protected updateFenceStandingInProfile(pmcData: IPmcData, fenceId: string, extractName: string): void protected getFenceStandingAfterExtract(pmcData: IPmcData, baseGain: number, extractCount: number): number
{ {
// Get current standing
const fenceId: string = Traders.FENCE;
let fenceStanding = Number(pmcData.TradersInfo[fenceId].standing); let fenceStanding = Number(pmcData.TradersInfo[fenceId].standing);
// Not exact replica of Live behaviour... Simplified for now. No real reason to do the whole (unconfirmed) // get standing after taking extract x times, x.xx format, gain from extract can be no smaller than 0.01
// extra 0.01 standing per day regeneration mechanic.
const baseGain: number = this.inraidConfig.carExtractBaseStandingGain;
const extractCount: number = pmcData.CarExtractCounts[extractName];
fenceStanding += Math.max(baseGain / extractCount, 0.01); fenceStanding += Math.max(baseGain / extractCount, 0.01);
// Ensure fence loyalty level is not above/below the range -7 - 15 // Ensure fence loyalty level is not above/below the range -7 to 15
const newFenceStanding = Math.min(Math.max(fenceStanding, -7), 15); const newFenceStanding = Math.min(Math.max(fenceStanding, -7), 15);
this.logger.debug(`Old vs new fence standing: ${pmcData.TradersInfo[fenceId].standing}, ${newFenceStanding}`); this.logger.debug(`Old vs new fence standing: ${pmcData.TradersInfo[fenceId].standing}, ${newFenceStanding}`);
pmcData.TradersInfo[fenceId].standing = newFenceStanding;
return Number(newFenceStanding.toFixed(2));
} }
} }

View File

@ -146,6 +146,7 @@ export class ProfileController
pmcData.Hideout.Seed = this.timeUtil.getTimestamp() + (8 * 60 * 60 * 24 * 365); // 8 years in future why? who knows, we saw it in live pmcData.Hideout.Seed = this.timeUtil.getTimestamp() + (8 * 60 * 60 * 24 * 365); // 8 years in future why? who knows, we saw it in live
pmcData.RepeatableQuests = []; pmcData.RepeatableQuests = [];
pmcData.CarExtractCounts = {}; pmcData.CarExtractCounts = {};
pmcData.CoopExtractCounts = {};
if (!pmcData.UnlockedInfo) if (!pmcData.UnlockedInfo)
{ {

View File

@ -459,9 +459,6 @@ export class Container
con.register<ScavCaseRewardGenerator>("ScavCaseRewardGenerator", ScavCaseRewardGenerator, { con.register<ScavCaseRewardGenerator>("ScavCaseRewardGenerator", ScavCaseRewardGenerator, {
lifecycle: Lifecycle.Singleton, lifecycle: Lifecycle.Singleton,
}); });
con.register<ScavCaseRewardGenerator>("ScavCaseRewardGenerator", ScavCaseRewardGenerator, {
lifecycle: Lifecycle.Singleton,
});
con.register<RagfairAssortGenerator>("RagfairAssortGenerator", { useClass: RagfairAssortGenerator }); con.register<RagfairAssortGenerator>("RagfairAssortGenerator", { useClass: RagfairAssortGenerator });
con.register<RagfairOfferGenerator>("RagfairOfferGenerator", { useClass: RagfairOfferGenerator }); con.register<RagfairOfferGenerator>("RagfairOfferGenerator", { useClass: RagfairOfferGenerator });
con.register<WeatherGenerator>("WeatherGenerator", { useClass: WeatherGenerator }); con.register<WeatherGenerator>("WeatherGenerator", { useClass: WeatherGenerator });

View File

@ -196,7 +196,7 @@ export class BotEquipmentModGenerator
// Get pool of mods that fit weapon // Get pool of mods that fit weapon
const compatibleModsPool = modPool[parentTemplate._id]; const compatibleModsPool = modPool[parentTemplate._id];
// Null guard against bad input weapon // biome-ignore lint/complexity/useSimplifiedLogicExpression: <explanation>
if ( if (
!((parentTemplate._props.Slots.length || parentTemplate._props.Cartridges?.length) !((parentTemplate._props.Slots.length || parentTemplate._props.Cartridges?.length)
|| parentTemplate._props.Chambers?.length) || parentTemplate._props.Chambers?.length)

View File

@ -121,9 +121,9 @@ export class BotGenerator
} }
this.logger.debug( this.logger.debug(
`Generated ${botGenerationDetails.botCountToGenerate} ${ `Generated ${botGenerationDetails.botCountToGenerate} ${output[0].Info.Settings.Role} (${
output[0].Info.Settings.Role botGenerationDetails.eventRole ?? ""
} (${botGenerationDetails.eventRole ?? ""}) bots`, }) bots`,
); );
return output; return output;

View File

@ -317,11 +317,7 @@ export class LootGenerator
} }
// Add preset to return object // Add preset to return object
itemsToReturn.push({ itemsToReturn.push({ count: 1, item_id: chosenWeaponPreset._id, isPreset: true });
count: 1,
item_id: chosenWeaponPreset._id,
isPreset: true,
});
// Get items related to chosen weapon // Get items related to chosen weapon
const linkedItemsToWeapon = this.ragfairLinkedItemService.getLinkedDbItems(chosenWeaponTpl); const linkedItemsToWeapon = this.ragfairLinkedItemService.getLinkedDbItems(chosenWeaponTpl);
@ -380,11 +376,7 @@ export class LootGenerator
// No need to add ammo to box, inventoryHelper.addItem() will handle it // No need to add ammo to box, inventoryHelper.addItem() will handle it
const chosenAmmoBox = this.randomUtil.getArrayValue(ammoBoxesMatchingCaliber); const chosenAmmoBox = this.randomUtil.getArrayValue(ammoBoxesMatchingCaliber);
rewards.push({ rewards.push({ count: rewardCount, item_id: chosenAmmoBox._id, isPreset: false });
count: rewardCount,
item_id: chosenAmmoBox._id,
isPreset: false,
});
continue; continue;
} }

View File

@ -8,6 +8,7 @@ import { RagfairServerHelper } from "@spt-aki/helpers/RagfairServerHelper";
import { RepeatableQuestHelper } from "@spt-aki/helpers/RepeatableQuestHelper"; import { RepeatableQuestHelper } from "@spt-aki/helpers/RepeatableQuestHelper";
import { Exit, ILocationBase } from "@spt-aki/models/eft/common/ILocationBase"; import { Exit, ILocationBase } from "@spt-aki/models/eft/common/ILocationBase";
import { TraderInfo } from "@spt-aki/models/eft/common/tables/IBotBase"; import { TraderInfo } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { import {
ICompletion, ICompletion,
ICompletionAvailableFor, ICompletionAvailableFor,
@ -157,10 +158,10 @@ export class RepeatableQuestGenerator
// a random combination of listed conditions can be required // a random combination of listed conditions can be required
// possible conditions elements and their relative probability can be defined in QuestConfig.js // possible conditions elements and their relative probability can be defined in QuestConfig.js
// We use ProbabilityObjectArray to draw by relative probability. e.g. for targets: // We use ProbabilityObjectArray to draw by relative probability. e.g. for targets:
// targets: { // "targets": {
// Savage: 7, // "Savage": 7,
// AnyPmc: 2, // "AnyPmc": 2,
// bossBully: 0.5 // "bossBully": 0.5
// } // }
// higher is more likely. We define the difficulty to be the inverse of the relative probability. // higher is more likely. We define the difficulty to be the inverse of the relative probability.
@ -497,7 +498,7 @@ export class RepeatableQuestGenerator
const levelsConfig = repeatableConfig.rewardScaling.levels; const levelsConfig = repeatableConfig.rewardScaling.levels;
const roublesConfig = repeatableConfig.rewardScaling.roubles; const roublesConfig = repeatableConfig.rewardScaling.roubles;
// in the available dumps only 2 distinct items were ever requested // In the available dumps only 2 distinct items were ever requested
let numberDistinctItems = 1; let numberDistinctItems = 1;
if (Math.random() > 0.75) if (Math.random() > 0.75)
{ {
@ -506,18 +507,20 @@ export class RepeatableQuestGenerator
const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side) as ICompletion; const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side) as ICompletion;
// Filter the items.json items to items the player must retrieve to complete queist: shouldn't be a quest item or "non-existant" // Filter the items.json items to items the player must retrieve to complete quest: shouldn't be a quest item or "non-existant"
let itemSelection = this.getRewardableItems(repeatableConfig); const possibleItemsToRetrievePool = this.getRewardableItems(repeatableConfig, traderId);
// Be fair, don't let the items be more expensive than the reward // Be fair, don't let the items be more expensive than the reward
let roublesBudget = Math.floor( let roublesBudget = Math.floor(
this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) * this.randomUtil.getFloat(0.5, 1), this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) * this.randomUtil.getFloat(0.5, 1),
); );
roublesBudget = Math.max(roublesBudget, 5000); roublesBudget = Math.max(roublesBudget, 5000);
itemSelection = itemSelection.filter((x) => this.itemHelper.getItemPrice(x[0]) < roublesBudget); let itemSelection = possibleItemsToRetrievePool.filter((x) =>
this.itemHelper.getItemPrice(x[0]) < roublesBudget
);
// We also have the option to use whitelist and/or blacklist which is defined in repeatableQuests.json as // We also have the option to use whitelist and/or blacklist which is defined in repeatableQuests.json as
// [{minPlayerLevel: 1, itemIds: ["id1",...]}, {minPlayerLevel: 15, itemIds: ["id3",...]}] // [{"minPlayerLevel": 1, "itemIds": ["id1",...]}, {"minPlayerLevel": 15, "itemIds": ["id3",...]}]
if (repeatableConfig.questConfig.Completion.useWhitelist) if (repeatableConfig.questConfig.Completion.useWhitelist)
{ {
const itemWhitelist = const itemWhitelist =
@ -870,14 +873,16 @@ export class RepeatableQuestGenerator
// Possible improvement -> draw trader-specific items e.g. with this.itemHelper.isOfBaseclass(val._id, ItemHelper.BASECLASS.FoodDrink) // Possible improvement -> draw trader-specific items e.g. with this.itemHelper.isOfBaseclass(val._id, ItemHelper.BASECLASS.FoodDrink)
let roublesBudget = rewardRoubles; let roublesBudget = rewardRoubles;
let chosenRewardItems = this.chooseRewardItemsWithinBudget(repeatableConfig, roublesBudget); let rewardItemPool = this.chooseRewardItemsWithinBudget(repeatableConfig, roublesBudget, traderId);
// Add xp reward
const rewards: IRewards = { const rewards: IRewards = {
Started: [], Started: [],
Success: [{ value: rewardXP, type: "Experience", index: 0 }], Success: [{ value: rewardXP, type: "Experience", index: 0 }],
Fail: [], Fail: [],
}; };
// Add money reward
if (traderId === Traders.PEACEKEEPER) if (traderId === Traders.PEACEKEEPER)
{ {
// convert to equivalent dollars // convert to equivalent dollars
@ -891,13 +896,14 @@ export class RepeatableQuestGenerator
} }
let index = 2; let index = 2;
if (chosenRewardItems.length > 0) if (rewardItemPool.length > 0)
{ {
let weaponRewardCount = 0;
for (let i = 0; i < rewardNumItems; i++) for (let i = 0; i < rewardNumItems; i++)
{ {
let value = 1; let itemCount = 1;
let children = null; let children: Item[] = null;
const itemSelected = chosenRewardItems[this.randomUtil.randInt(chosenRewardItems.length)]; const itemSelected = rewardItemPool[this.randomUtil.randInt(rewardItemPool.length)];
if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.AMMO)) if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.AMMO))
{ {
// Dont reward ammo that stacks to less than what's defined in config // Dont reward ammo that stacks to less than what's defined in config
@ -906,39 +912,55 @@ export class RepeatableQuestGenerator
continue; continue;
} }
// If we provide ammo we don't want to provide just one bullet // Randomise the cartridge count returned
value = this.randomUtil.randInt( itemCount = this.randomUtil.randInt(
repeatableConfig.rewardAmmoStackMinSize, repeatableConfig.rewardAmmoStackMinSize,
itemSelected._props.StackMaxSize, itemSelected._props.StackMaxSize,
); );
} }
else if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.WEAPON)) else if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.WEAPON))
{ {
const defaultPreset = this.presetHelper.getDefaultPreset(itemSelected._id); if (weaponRewardCount >= 1)
if (defaultPreset)
{ {
children = this.ragfairServerHelper.reparentPresets( // Limit weapon rewards to 1 per daily
defaultPreset._items[0], rewardItemPool = rewardItemPool.filter((x) =>
defaultPreset._items, !this.itemHelper.isOfBaseclass(x._id, BaseClasses.WEAPON)
); );
continue;
} }
let defaultPreset = this.presetHelper.getDefaultPreset(itemSelected._id);
if (!defaultPreset)
{
// No default for chosen weapon found, get any random default weapon preset
const defaultPresets = Object.values(this.presetHelper.getDefaultPresets());
defaultPreset = this.randomUtil.getArrayValue(defaultPresets);
} }
rewards.Success.push(this.generateRewardItem(itemSelected._id, value, index, children));
// TODO: maybe also non-default use ragfair to calculate the price children = this.ragfairServerHelper.reparentPresets(defaultPreset._items[0], defaultPreset._items);
// this.ragfairServer.getWeaponPresetPrice(item, items, existingPrice) weaponRewardCount++;
}
roublesBudget -= value * this.itemHelper.getStaticItemPrice(itemSelected._id); // 25% chance to double reward stack (item should be stackable and not weapon)
if (this.increaseRewardItemStackSize(itemSelected))
{
itemCount = 2;
}
rewards.Success.push(this.generateRewardItem(itemSelected._id, itemCount, index, children));
const itemCost = (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.WEAPON))
? this.itemHelper.getItemMaxPrice(children[0]._tpl) // use if preset is not default : this.itemHelper.getWeaponPresetPrice(children[0], children, this.itemHelper.getStaticItemPrice(itemSelected._id))
: this.itemHelper.getStaticItemPrice(itemSelected._id);
roublesBudget -= itemCount * itemCost;
index += 1; index += 1;
// if we still have budget narrow down the items // if we still have budget narrow down the items
if (roublesBudget > 0) if (roublesBudget > 0)
{ {
// Filter possible reward items to only items with a price below the remaining budget // Filter possible reward items to only items with a price below the remaining budget
chosenRewardItems = chosenRewardItems.filter((x) => rewardItemPool = rewardItemPool.filter((x) =>
this.itemHelper.getStaticItemPrice(x._id) < roublesBudget this.itemHelper.getStaticItemPrice(x._id) < roublesBudget
); );
if (chosenRewardItems.length === 0) if (rewardItemPool.length === 0)
{ {
break; // No reward items left, exit break; // No reward items left, exit
} }
@ -972,6 +994,18 @@ export class RepeatableQuestGenerator
return rewards; return rewards;
} }
/**
* Should reward item have stack size increased (25% chance)
* @param item Item to possibly increase stack size of
* @returns True if it should
*/
protected increaseRewardItemStackSize(item: ITemplateItem): boolean
{
return item._props.StackMaxSize > 1
&& !this.itemHelper.isOfBaseclass(item._id, BaseClasses.WEAPON)
&& this.randomUtil.getChance100(25);
}
/** /**
* Select a number of items that have a colelctive value of the passed in parameter * Select a number of items that have a colelctive value of the passed in parameter
* @param repeatableConfig Config * @param repeatableConfig Config
@ -981,15 +1015,17 @@ export class RepeatableQuestGenerator
protected chooseRewardItemsWithinBudget( protected chooseRewardItemsWithinBudget(
repeatableConfig: IRepeatableQuestConfig, repeatableConfig: IRepeatableQuestConfig,
roublesBudget: number, roublesBudget: number,
traderId: string,
): ITemplateItem[] ): ITemplateItem[]
{ {
// First filter for type and baseclass to avoid lookup in handbook for non-available items // First filter for type and baseclass to avoid lookup in handbook for non-available items
const rewardableItems = this.getRewardableItems(repeatableConfig); const rewardableItemPool = this.getRewardableItems(repeatableConfig, traderId);
const minPrice = Math.min(25000, 0.5 * roublesBudget); const minPrice = Math.min(25000, 0.5 * roublesBudget);
let itemSelection = rewardableItems.filter((x) =>
let rewardableItemPoolWithinBudget = rewardableItemPool.filter((x) =>
this.itemHelper.getItemPrice(x[0]) < roublesBudget && this.itemHelper.getItemPrice(x[0]) > minPrice this.itemHelper.getItemPrice(x[0]) < roublesBudget && this.itemHelper.getItemPrice(x[0]) > minPrice
).map((x) => x[1]); ).map((x) => x[1]);
if (itemSelection.length === 0) if (rewardableItemPoolWithinBudget.length === 0)
{ {
this.logger.warning( this.logger.warning(
this.localisationService.getText("repeatable-no_reward_item_found_in_price_range", { this.localisationService.getText("repeatable-no_reward_item_found_in_price_range", {
@ -998,12 +1034,12 @@ export class RepeatableQuestGenerator
}), }),
); );
// In case we don't find any items in the price range // In case we don't find any items in the price range
itemSelection = rewardableItems.filter((x) => this.itemHelper.getItemPrice(x[0]) < roublesBudget).map((x) => rewardableItemPoolWithinBudget = rewardableItemPool.filter((x) =>
x[1] this.itemHelper.getItemPrice(x[0]) < roublesBudget
); ).map((x) => x[1]);
} }
return itemSelection; return rewardableItemPoolWithinBudget;
} }
/** /**
@ -1037,12 +1073,17 @@ export class RepeatableQuestGenerator
* @param repeatableQuestConfig Config file * @param repeatableQuestConfig Config file
* @returns List of rewardable items [[_tpl, itemTemplate],...] * @returns List of rewardable items [[_tpl, itemTemplate],...]
*/ */
protected getRewardableItems(repeatableQuestConfig: IRepeatableQuestConfig): [string, ITemplateItem][] protected getRewardableItems(
repeatableQuestConfig: IRepeatableQuestConfig,
traderId: string,
): [string, ITemplateItem][]
{ {
// check for specific baseclasses which don't make sense as reward item // check for specific baseclasses which don't make sense as reward item
// also check if the price is greater than 0; there are some items whose price can not be found // also check if the price is greater than 0; there are some items whose price can not be found
// those are not in the game yet (e.g. AGS grenade launcher) // those are not in the game yet (e.g. AGS grenade launcher)
return Object.entries(this.databaseServer.getTables().templates.items).filter(([tpl, itemTemplate]) => return Object.entries(this.databaseServer.getTables().templates.items).filter(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
([tpl, itemTemplate]) =>
{ {
// Base "Item" item has no parent, ignore it // Base "Item" item has no parent, ignore it
if (itemTemplate._parent === "") if (itemTemplate._parent === "")
@ -1050,8 +1091,10 @@ export class RepeatableQuestGenerator
return false; return false;
} }
return this.isValidRewardItem(tpl, repeatableQuestConfig); const traderWhitelist = repeatableQuestConfig.traderWhitelist.find((x) => x.traderId === traderId);
}); return this.isValidRewardItem(tpl, repeatableQuestConfig, traderWhitelist?.rewardBaseWhitelist);
},
);
} }
/** /**
@ -1060,12 +1103,21 @@ export class RepeatableQuestGenerator
* @param {string} tpl template id of item to check * @param {string} tpl template id of item to check
* @returns True if item is valid reward * @returns True if item is valid reward
*/ */
protected isValidRewardItem(tpl: string, repeatableQuestConfig: IRepeatableQuestConfig): boolean protected isValidRewardItem(
tpl: string,
repeatableQuestConfig: IRepeatableQuestConfig,
itemBaseWhitelist: string[],
): boolean
{ {
let valid = this.itemHelper.isValidItem(tpl); if (!this.itemHelper.isValidItem(tpl))
if (!valid)
{ {
return valid; return false;
}
// Check global blacklist
if (this.itemFilterService.isItemBlacklisted(tpl))
{
return false;
} }
// Item is on repeatable or global blacklist // Item is on repeatable or global blacklist
@ -1080,24 +1132,22 @@ export class RepeatableQuestGenerator
return false; return false;
} }
if ( // Skip boss items
this.itemHelper.isOfBaseclasses(tpl, [ if (this.itemFilterService.isBossItem(tpl))
BaseClasses.DOG_TAG_USEC,
BaseClasses.DOG_TAG_BEAR,
BaseClasses.MOUNT,
BaseClasses.KEY,
BaseClasses.ARMBAND,
])
)
{ {
return false; return false;
} }
// Skip globally blacklisted items + boss items // Trader has specific item base types they can give as rewards to player
// biome-ignore lint/complexity/useSimplifiedLogicExpression: <explanation> if (itemBaseWhitelist !== undefined)
valid = !this.itemFilterService.isItemBlacklisted(tpl) && !this.itemFilterService.isBossItem(tpl); {
if (!this.itemHelper.isOfBaseclasses(tpl, [...itemBaseWhitelist]))
{
return false;
}
}
return valid; return true;
} }
/** /**

View File

@ -197,9 +197,6 @@ export class InRaidHelper
profileData.Info.Experience += profileData.Stats.Eft.TotalSessionExperience; profileData.Info.Experience += profileData.Stats.Eft.TotalSessionExperience;
profileData.Stats.Eft.TotalSessionExperience = 0; profileData.Stats.Eft.TotalSessionExperience = 0;
// Remove the Lab card
this.removeMapAccessKey(saveProgressRequest, sessionID);
this.setPlayerInRaidLocationStatusToNone(sessionID); this.setPlayerInRaidLocationStatusToNone(sessionID);
if (!saveProgressRequest.isPlayerScav) if (!saveProgressRequest.isPlayerScav)
@ -332,32 +329,6 @@ export class InRaidHelper
} }
} }
/**
* Some maps have one-time-use keys (e.g. Labs
* Remove the relevant key from an inventory based on the post-raid request data passed in
* @param offraidData post-raid data
* @param sessionID Session id
*/
protected removeMapAccessKey(offraidData: ISaveProgressRequestData, sessionID: string): void
{
const locationName = this.saveServer.getProfile(sessionID).inraid.location.toLowerCase();
const mapKey = this.databaseServer.getTables().locations[locationName].base.AccessKeys[0];
if (!mapKey)
{
return;
}
for (const item of offraidData.profile.Inventory.items)
{
if (item._tpl === mapKey && item.slotId.toLowerCase() !== "hideout")
{
this.inventoryHelper.removeItem(offraidData.profile, item._id, sessionID);
break;
}
}
}
/** /**
* Set the SPT inraid location Profile property to 'none' * Set the SPT inraid location Profile property to 'none'
* @param sessionID Session id * @param sessionID Session id

View File

@ -30,7 +30,8 @@ export interface IBotBase
RepeatableQuests: IPmcDataRepeatableQuest[]; RepeatableQuests: IPmcDataRepeatableQuest[];
Bonuses: Bonus[]; Bonuses: Bonus[];
Notes: Notes; Notes: Notes;
CarExtractCounts: CarExtractCounts; CarExtractCounts: Record<string, number>;
CoopExtractCounts: Record<string, number>;
SurvivorClass: SurvivorClass; SurvivorClass: SurvivorClass;
WishList: string[]; WishList: string[];
/** SPT specific property used during bot generation in raid */ /** SPT specific property used during bot generation in raid */
@ -387,6 +388,8 @@ export interface Productive
/** Used when sending data to client */ /** Used when sending data to client */
NeedFuelForAllProductionTime?: boolean; NeedFuelForAllProductionTime?: boolean;
sptIsScavCase?: boolean; sptIsScavCase?: boolean;
/** Some crafts are always inProgress, but need to be reset, e.g. water collector */
sptIsComplete?: boolean;
} }
export interface Production extends Productive export interface Production extends Productive

View File

@ -3,14 +3,20 @@ import { Dialogue, IUserBuilds } from "@spt-aki/models/eft/profile/IAkiProfile";
export interface IProfileTemplates export interface IProfileTemplates
{ {
/* eslint-disable @typescript-eslint/naming-convention */
Standard: IProfileSides; Standard: IProfileSides;
"Left Behind": IProfileSides; "Left Behind": IProfileSides;
"Prepare To Escape": IProfileSides; "Prepare To Escape": IProfileSides;
"Edge Of Darkness": IProfileSides; "Edge Of Darkness": IProfileSides;
"SPT Developer": IProfileSides;
"SPT Easy start": IProfileSides;
"SPT Zero to hero": IProfileSides;
/* eslint-enable @typescript-eslint/naming-convention */
} }
export interface IProfileSides export interface IProfileSides
{ {
descriptionLocaleKey: string;
usec: TemplateSide; usec: TemplateSide;
bear: TemplateSide; bear: TemplateSide;
} }

View File

@ -1,7 +1,9 @@
// biome-ignore lint/suspicious/noEmptyInterface: <explanation>
export interface IAcceptFriendRequestData extends IBaseFriendRequest export interface IAcceptFriendRequestData extends IBaseFriendRequest
{ {
} }
// biome-ignore lint/suspicious/noEmptyInterface: <explanation>
export interface ICancelFriendRequestData extends IBaseFriendRequest export interface ICancelFriendRequestData extends IBaseFriendRequest
{ {
} }

View File

@ -3,7 +3,7 @@ import { IProcessBaseTradeRequestData } from "@spt-aki/models/eft/trade/IProcess
export interface IProcessBuyTradeRequestData extends IProcessBaseTradeRequestData export interface IProcessBuyTradeRequestData extends IProcessBaseTradeRequestData
{ {
Action: "buy_from_trader" | "TradingConfirm" | "RestoreHealth" | ""; Action: "buy_from_trader" | "TradingConfirm" | "RestoreHealth" | "SptInsure" | "SptRepair" | "";
type: string; type: string;
tid: string; tid: string;
item_id: string; item_id: string;
@ -14,6 +14,7 @@ export interface IProcessBuyTradeRequestData extends IProcessBaseTradeRequestDat
export interface SchemeItem export interface SchemeItem
{ {
/** Id of stack to take money from, is money tpl when Action is `SptInsure` */
id: string; id: string;
count: number; count: number;
} }

View File

@ -40,6 +40,6 @@ export enum WildSpawnTypeNumber
PEACEFULLZRYACHIYEVENT = 38, PEACEFULLZRYACHIYEVENT = 38,
SECTACTPRIESTEVENT = 39, SECTACTPRIESTEVENT = 39,
RAVANGEZRYACHIYEVENT = 40, RAVANGEZRYACHIYEVENT = 40,
SPTUSEC = 41, SPTUSEC = 38,
SPTBEAR = 42, SPTBEAR = 39,
} }

View File

@ -12,8 +12,10 @@ export interface IInRaidConfig extends IBaseConfig
carExtracts: string[]; carExtracts: string[];
/** Names of coop extracts */ /** Names of coop extracts */
coopExtracts: string[]; coopExtracts: string[];
/** Fene rep gain from a single car extract */ /** Fence rep gain from a single car extract */
carExtractBaseStandingGain: number; carExtractBaseStandingGain: number;
/** Fence rep gain from a single coop extract */
coopExtractBaseStandingGain: number;
/** Fence rep gain when successfully extracting as pscav */ /** Fence rep gain when successfully extracting as pscav */
scavExtractGain: number; scavExtractGain: number;
/** On death should items in your secure keep their Find in raid status regardless of how you finished the raid */ /** On death should items in your secure keep their Find in raid status regardless of how you finished the raid */

View File

@ -76,6 +76,7 @@ export interface ITraderWhitelist
{ {
traderId: string; traderId: string;
questTypes: string[]; questTypes: string[];
rewardBaseWhitelist: string[];
} }
export interface IRepeatableQuestTypesConfig export interface IRepeatableQuestTypesConfig

View File

@ -83,6 +83,9 @@ export class EventOutputHolder
); );
profileChanges.improvements = this.jsonUtil.clone(this.getImprovementsFromProfileAndFlagComplete(pmcData)); profileChanges.improvements = this.jsonUtil.clone(this.getImprovementsFromProfileAndFlagComplete(pmcData));
profileChanges.traderRelations = this.constructTraderRelations(pmcData.TradersInfo); profileChanges.traderRelations = this.constructTraderRelations(pmcData.TradersInfo);
// Fixes container craft from water collector not resetting after collection
this.resetSptIsCompleteFlaggedCrafts(pmcData.Hideout.Production);
} }
/** /**
@ -153,6 +156,13 @@ export class EventOutputHolder
continue; continue;
} }
// Complete and is a water collector craft
// Needed as canister craft (water collector) is continuous
if (production.sptIsComplete && productionKey === "5d5589c1f934db045e6c5492")
{
continue;
}
// Skip completed // Skip completed
if (!production.inProgress) if (!production.inProgress)
{ {
@ -176,4 +186,22 @@ export class EventOutputHolder
return productions; return productions;
} }
/**
* Required as continuous productions don't reset and stay at 100% completion but client thinks it hasn't started
* @param productions Productions in a profile
*/
protected resetSptIsCompleteFlaggedCrafts(productions: Record<string, Productive>): void
{
for (const productionKey in productions)
{
const production = productions[productionKey];
if (production.sptIsComplete)
{
production.sptIsComplete = false;
production.Progress = 0;
production.StartTimestamp = this.timeUtil.getTimestamp();
}
}
}
} }

View File

@ -6,11 +6,6 @@ import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile";
@injectable() @injectable()
export class InsuranceSaveLoadRouter extends SaveLoadRouter export class InsuranceSaveLoadRouter extends SaveLoadRouter
{ {
constructor()
{
super();
}
public override getHandledRoutes(): HandledRoute[] public override getHandledRoutes(): HandledRoute[]
{ {
return [new HandledRoute("aki-insurance", false)]; return [new HandledRoute("aki-insurance", false)];

View File

@ -7,11 +7,6 @@ import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile";
@injectable() @injectable()
export class ProfileSaveLoadRouter extends SaveLoadRouter export class ProfileSaveLoadRouter extends SaveLoadRouter
{ {
constructor()
{
super();
}
public override getHandledRoutes(): HandledRoute[] public override getHandledRoutes(): HandledRoute[]
{ {
return [new HandledRoute("aki-profile", false)]; return [new HandledRoute("aki-profile", false)];

View File

@ -51,9 +51,7 @@ export class PaymentService
// Track the amounts of each type of currency involved in the trade. // Track the amounts of each type of currency involved in the trade.
const currencyAmounts: { [key: string]: number; } = {}; const currencyAmounts: { [key: string]: number; } = {};
// Delete barter items and track currencies if the action is "TradingConfirm". // Delete barter items and track currencies
if (request.Action === "TradingConfirm")
{
for (const index in request.scheme_items) for (const index in request.scheme_items)
{ {
// Find the corresponding item in the player's inventory. // Find the corresponding item in the player's inventory.
@ -69,10 +67,15 @@ export class PaymentService
else else
{ {
// If the item is money, add its count to the currencyAmounts object. // If the item is money, add its count to the currencyAmounts object.
currencyAmounts[item._tpl] = (currencyAmounts[item._tpl] || 0) currencyAmounts[item._tpl] = (currencyAmounts[item._tpl] || 0) + request.scheme_items[index].count;
+ request.scheme_items[index].count;
} }
} }
else
{
// Used by `SptInsure`
// Handle differently, `id` is the money type tpl
const currencyTpl = request.scheme_items[index].id;
currencyAmounts[currencyTpl] = (currencyAmounts[currencyTpl] || 0) + request.scheme_items[index].count;
} }
} }
@ -87,6 +90,7 @@ export class PaymentService
if (currencyAmount > 0) if (currencyAmount > 0)
{ {
// Find money stacks in inventory and remove amount needed + update output object to inform client of changes
output = this.addPaymentToOutput(pmcData, currencyTpl, currencyAmount, sessionID, output); output = this.addPaymentToOutput(pmcData, currencyTpl, currencyAmount, sessionID, output);
// If there are warnings, exit early. // If there are warnings, exit early.
@ -214,6 +218,7 @@ export class PaymentService
{ {
const request = { const request = {
items: [{ items: [{
// eslint-disable-next-line @typescript-eslint/naming-convention
item_id: currency, item_id: currency,
count: calcAmount, count: calcAmount,
}], }],
@ -258,7 +263,7 @@ export class PaymentService
} }
/** /**
* Remove currency from player stash/inventory * Remove currency from player stash/inventory and update client object with changes
* @param pmcData Player profile to find and remove currency from * @param pmcData Player profile to find and remove currency from
* @param currencyTpl Type of currency to pay * @param currencyTpl Type of currency to pay
* @param amountToPay money value to pay * @param amountToPay money value to pay

View File

@ -6,8 +6,8 @@ import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
import { TraderHelper } from "@spt-aki/helpers/TraderHelper"; import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { IHideoutImprovement } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Bonus, HideoutSlot, IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase"; import { Bonus, HideoutSlot, IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase";
import { IHideoutImprovement } from "@spt-aki/models/eft/common/tables/IBotBase";
import { IPmcDataRepeatableQuest, IRepeatableQuest } from "@spt-aki/models/eft/common/tables/IRepeatableQuests"; import { IPmcDataRepeatableQuest, IRepeatableQuest } from "@spt-aki/models/eft/common/tables/IRepeatableQuests";
import { StageBonus } from "@spt-aki/models/eft/hideout/IHideoutArea"; import { StageBonus } from "@spt-aki/models/eft/hideout/IHideoutArea";
import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile"; import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile";
@ -83,7 +83,7 @@ export class ProfileFixerService
this.reorderHideoutAreasWithResouceInputs(pmcProfile); this.reorderHideoutAreasWithResouceInputs(pmcProfile);
if ( if (
pmcProfile.Hideout.Areas[HideoutAreas.GENERATOR].slots.length pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.GENERATOR).slots.length
< (6 < (6
+ this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots + this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots
.Generator.Slots) .Generator.Slots)
@ -100,7 +100,7 @@ export class ProfileFixerService
} }
if ( if (
pmcProfile.Hideout.Areas[HideoutAreas.WATER_COLLECTOR].slots.length pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.WATER_COLLECTOR).slots.length
< (1 < (1
+ this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots + this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots
.WaterCollector.Slots) .WaterCollector.Slots)
@ -117,7 +117,7 @@ export class ProfileFixerService
} }
if ( if (
pmcProfile.Hideout.Areas[HideoutAreas.AIR_FILTERING].slots.length pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.AIR_FILTERING).slots.length
< (3 < (3
+ this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots + this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots
.AirFilteringUnit.Slots) .AirFilteringUnit.Slots)
@ -135,7 +135,7 @@ export class ProfileFixerService
// BTC Farm doesnt have extra slots for hideout management, but we still check for modded stuff!! // BTC Farm doesnt have extra slots for hideout management, but we still check for modded stuff!!
if ( if (
pmcProfile.Hideout.Areas[HideoutAreas.BITCOIN_FARM].slots.length pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.BITCOIN_FARM).slots.length
< (50 < (50
+ this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots + this.databaseServer.getTables().globals.config.SkillsSettings.HideoutManagement.EliteSlots
.BitcoinFarm.Slots) .BitcoinFarm.Slots)
@ -571,7 +571,7 @@ export class ProfileFixerService
*/ */
protected addMissingWallImprovements(pmcProfile: IPmcData): void protected addMissingWallImprovements(pmcProfile: IPmcData): void
{ {
const profileWallArea = pmcProfile.Hideout.Areas[HideoutAreas.EMERGENCY_WALL]; const profileWallArea = pmcProfile.Hideout.Areas.find((x) => x.type === HideoutAreas.EMERGENCY_WALL);
const wallDb = this.databaseServer.getTables().hideout.areas.find((x) => const wallDb = this.databaseServer.getTables().hideout.areas.find((x) =>
x.type === HideoutAreas.EMERGENCY_WALL x.type === HideoutAreas.EMERGENCY_WALL
); );
@ -647,8 +647,7 @@ export class ProfileFixerService
for (const areaId of areasToCheck) for (const areaId of areasToCheck)
{ {
const area = pmcProfile.Hideout.Areas[areaId]; const area = pmcProfile.Hideout.Areas.find((x) => x.type === areaId);
if (!area) if (!area)
{ {
this.logger.debug(`unable to sort ${areaId} slots, no area found`); this.logger.debug(`unable to sort ${areaId} slots, no area found`);

View File

@ -239,9 +239,6 @@ export class RagfairOfferService
const sessionID = profile.sessionId; const sessionID = profile.sessionId;
const offerIndex = profile.RagfairInfo.offers.findIndex((o) => o._id === offer._id); const offerIndex = profile.RagfairInfo.offers.findIndex((o) => o._id === offer._id);
profile.RagfairInfo.rating -= this.ragfairConfig.sell.reputation.loss;
profile.RagfairInfo.isRatingGrowing = false;
if (offerIndex === -1) if (offerIndex === -1)
{ {
this.logger.warning(this.localisationService.getText("ragfair-unable_to_find_offer_to_remove", offer._id)); this.logger.warning(this.localisationService.getText("ragfair-unable_to_find_offer_to_remove", offer._id));
@ -251,6 +248,9 @@ export class RagfairOfferService
); );
} }
profile.RagfairInfo.rating -= this.ragfairConfig.sell.reputation.loss;
profile.RagfairInfo.isRatingGrowing = false;
if (offer.items[0].upd.StackObjectsCount > offer.items[0].upd.OriginalStackObjectsCount) if (offer.items[0].upd.StackObjectsCount > offer.items[0].upd.OriginalStackObjectsCount)
{ {
offer.items[0].upd.StackObjectsCount = offer.items[0].upd.OriginalStackObjectsCount; offer.items[0].upd.StackObjectsCount = offer.items[0].upd.OriginalStackObjectsCount;

View File

@ -123,7 +123,7 @@ export class RepairService
const options: IProcessBuyTradeRequestData = { const options: IProcessBuyTradeRequestData = {
scheme_items: [{ id: repairedItemId, count: Math.round(repairCost) }], scheme_items: [{ id: repairedItemId, count: Math.round(repairCost) }],
tid: traderId, tid: traderId,
Action: "", Action: "SptRepair",
type: "", type: "",
item_id: "", item_id: "",
count: 0, count: 0,