Merge branch 'master' of https://dev.sp-tarkov.com/SPT-AKI/Server into 3.8.0
# Conflicts: # project/src/callbacks/BundleCallbacks.ts # project/src/controllers/RepeatableQuestController.ts # project/src/helpers/InRaidHelper.ts # project/src/loaders/PreAkiModLoader.ts
This commit is contained in:
commit
268a9b8abd
@ -1,4 +1,4 @@
|
|||||||
FROM node:14 as builder
|
FROM node:18-alpine as builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@ -8,6 +8,6 @@ RUN npm i
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run gen:docs
|
RUN npm run gen:docs
|
||||||
|
|
||||||
FROM nginx:latest
|
FROM nginx:stable-alpine3.17
|
||||||
|
|
||||||
COPY --from=builder /app/docs /usr/share/nginx/html
|
COPY --from=builder /app/docs /usr/share/nginx/html
|
@ -763,7 +763,9 @@
|
|||||||
"64e74a2fc2b4f829615ec332",
|
"64e74a2fc2b4f829615ec332",
|
||||||
"64e74a274d49d23b2c39d317",
|
"64e74a274d49d23b2c39d317",
|
||||||
"64f09c02b63b74469b6c149f",
|
"64f09c02b63b74469b6c149f",
|
||||||
"64f07f7726cfa02c506f8ac0"
|
"64f07f7726cfa02c506f8ac0",
|
||||||
|
"64f69b4267e11a7c6206e010",
|
||||||
|
"64f5b4f71a5f313cb144c06c"
|
||||||
],
|
],
|
||||||
"laboratory": [
|
"laboratory": [
|
||||||
"6398a4cfb5992f573c6562b3",
|
"6398a4cfb5992f573c6562b3",
|
||||||
|
@ -617,39 +617,39 @@
|
|||||||
"gifterSettings": [{
|
"gifterSettings": [{
|
||||||
"map": "bigmap",
|
"map": "bigmap",
|
||||||
"zones": "ZoneDormitory,ZoneGasStation,ZoneScavBase",
|
"zones": "ZoneDormitory,ZoneGasStation,ZoneScavBase",
|
||||||
"spawnChance": 15
|
"spawnChance": 45
|
||||||
}, {
|
}, {
|
||||||
"map": "factory4_day",
|
"map": "factory4_day",
|
||||||
"zones": "BotZone",
|
"zones": "BotZone",
|
||||||
"spawnChance": 10
|
"spawnChance": 30
|
||||||
}, {
|
}, {
|
||||||
"map": "laboratory",
|
"map": "laboratory",
|
||||||
"zones": "BotZoneFloor1,BotZoneFloor2",
|
"zones": "BotZoneFloor1,BotZoneFloor2",
|
||||||
"spawnChance": 15
|
"spawnChance": 35
|
||||||
}, {
|
}, {
|
||||||
"map": "interchange",
|
"map": "interchange",
|
||||||
"zones": "ZoneCenterBot,ZoneCenter,ZoneOLI,ZoneIDEA,ZoneGoshan,ZoneIDEAPark,ZoneOLIPark",
|
"zones": "ZoneCenterBot,ZoneCenter,ZoneOLI,ZoneIDEA,ZoneGoshan,ZoneIDEAPark,ZoneOLIPark",
|
||||||
"spawnChance": 15
|
"spawnChance": 45
|
||||||
}, {
|
}, {
|
||||||
"map": "lighthouse",
|
"map": "lighthouse",
|
||||||
"zones": "Zone_TreatmentContainers,Zone_Chalet,Zone_RoofContainers,Zone_RoofBeach,Zone_Hellicopter",
|
"zones": "Zone_TreatmentContainers,Zone_Chalet,Zone_RoofContainers,Zone_RoofBeach,Zone_Hellicopter",
|
||||||
"spawnChance": 15
|
"spawnChance": 45
|
||||||
}, {
|
}, {
|
||||||
"map": "rezervbase",
|
"map": "rezervbase",
|
||||||
"zones": "ZoneRailStrorage,ZoneRailStrorage,ZoneRailStrorage,ZonePTOR1,ZonePTOR2,ZoneBarrack,ZoneBarrack,ZoneBarrack,ZoneSubStorage",
|
"zones": "ZoneRailStrorage,ZoneRailStrorage,ZoneRailStrorage,ZonePTOR1,ZonePTOR2,ZoneBarrack,ZoneBarrack,ZoneBarrack,ZoneSubStorage",
|
||||||
"spawnChance": 15
|
"spawnChance": 45
|
||||||
}, {
|
}, {
|
||||||
"map": "shoreline",
|
"map": "shoreline",
|
||||||
"zones": "ZonePort,ZoneGreenHouses,ZoneSanatorium1,ZoneGreenHouses,ZoneSanatorium2,ZoneMeteoStation,ZoneSanatorium1,ZoneSanatorium2",
|
"zones": "ZonePort,ZoneGreenHouses,ZoneSanatorium1,ZoneGreenHouses,ZoneSanatorium2,ZoneMeteoStation,ZoneSanatorium1,ZoneSanatorium2",
|
||||||
"spawnChance": 15
|
"spawnChance": 45
|
||||||
}, {
|
}, {
|
||||||
"map": "tarkovstreets",
|
"map": "tarkovstreets",
|
||||||
"zones": "ZoneColumn",
|
"zones": "ZoneColumn",
|
||||||
"spawnChance": 10
|
"spawnChance": 40
|
||||||
}, {
|
}, {
|
||||||
"map": "woods",
|
"map": "woods",
|
||||||
"zones": "ZoneScavBase2,ZoneWoodCutter",
|
"zones": "ZoneScavBase2,ZoneWoodCutter",
|
||||||
"spawnChance": 15
|
"spawnChance": 45
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"events": [{
|
"events": [{
|
||||||
|
@ -8777,8 +8777,7 @@
|
|||||||
"mod_magazine": [
|
"mod_magazine": [
|
||||||
"55d4887d4bdc2d962f8b4570",
|
"55d4887d4bdc2d962f8b4570",
|
||||||
"5c05413a0db834001c390617",
|
"5c05413a0db834001c390617",
|
||||||
"5aaa5dfee5b5b000140293d3",
|
"5aaa5dfee5b5b000140293d3"
|
||||||
"6241c2c2117ad530666a5108"
|
|
||||||
],
|
],
|
||||||
"mod_pistol_grip": [
|
"mod_pistol_grip": [
|
||||||
"5c0e2ff6d174af02a1659d4a",
|
"5c0e2ff6d174af02a1659d4a",
|
||||||
|
@ -8774,8 +8774,7 @@
|
|||||||
"mod_magazine": [
|
"mod_magazine": [
|
||||||
"55d4887d4bdc2d962f8b4570",
|
"55d4887d4bdc2d962f8b4570",
|
||||||
"5c05413a0db834001c390617",
|
"5c05413a0db834001c390617",
|
||||||
"5aaa5dfee5b5b000140293d3",
|
"5aaa5dfee5b5b000140293d3"
|
||||||
"6241c2c2117ad530666a5108"
|
|
||||||
],
|
],
|
||||||
"mod_pistol_grip": [
|
"mod_pistol_grip": [
|
||||||
"5c0e2ff6d174af02a1659d4a",
|
"5c0e2ff6d174af02a1659d4a",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,8 @@ export class BundleCallbacks
|
|||||||
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendBundle(sessionID: string, req: any, resp: any, body: any): any
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
public sendBundle(sessionID: string, req: any, resp: any, body: any): void
|
||||||
{
|
{
|
||||||
this.logger.info(`[BUNDLE]: ${req.url}`);
|
this.logger.info(`[BUNDLE]: ${req.url}`);
|
||||||
|
|
||||||
@ -38,12 +39,14 @@ export class BundleCallbacks
|
|||||||
/**
|
/**
|
||||||
* Handle singleplayer/bundles
|
* Handle singleplayer/bundles
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public getBundles(url: string, info: any, sessionID: string): string
|
public getBundles(url: string, info: any, sessionID: string): string
|
||||||
{
|
{
|
||||||
const local = this.httpConfig.ip === "127.0.0.1" || this.httpConfig.ip === "localhost";
|
const local = this.httpConfig.ip === "127.0.0.1" || this.httpConfig.ip === "localhost";
|
||||||
return this.httpResponse.noBody(this.bundleLoader.getBundles(local));
|
return this.httpResponse.noBody(this.bundleLoader.getBundles(local));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public getBundle(url: string, info: any, sessionID: string): string
|
public getBundle(url: string, info: any, sessionID: string): string
|
||||||
{
|
{
|
||||||
return "BUNDLE";
|
return "BUNDLE";
|
||||||
|
@ -864,15 +864,21 @@ export class HideoutController
|
|||||||
globals.config.SkillsSettings.HideoutManagement.SkillPointsPerCraft,
|
globals.config.SkillsSettings.HideoutManagement.SkillPointsPerCraft,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
// manager Crafting skill
|
// Manager Crafting skill
|
||||||
if (craftingExpAmount > 0)
|
if (craftingExpAmount > 0)
|
||||||
{
|
{
|
||||||
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.CRAFTING, craftingExpAmount);
|
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.CRAFTING, craftingExpAmount);
|
||||||
this.profileHelper.addSkillPointsToPlayer(
|
|
||||||
pmcData,
|
const intellectAmountToGive = 0.5 * (Math.round(craftingExpAmount / 15));
|
||||||
SkillTypes.INTELLECT,
|
if (intellectAmountToGive > 0)
|
||||||
0.5 * (Math.round(craftingExpAmount / 15)),
|
{
|
||||||
);
|
this.profileHelper.addSkillPointsToPlayer(
|
||||||
|
pmcData,
|
||||||
|
SkillTypes.INTELLECT,
|
||||||
|
intellectAmountToGive,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
area.lastRecipe = request.recipeId;
|
area.lastRecipe = request.recipeId;
|
||||||
counterHoursCrafting.value = hoursCrafting;
|
counterHoursCrafting.value = hoursCrafting;
|
||||||
|
@ -72,8 +72,8 @@ export class RepeatableQuestController
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* The method checks if the player level requirement for repeatable quests (e.g. daily lvl5, weekly lvl15) is met and if the previously active quests
|
* The method checks if the player level requirement for repeatable quests (e.g. daily lvl5, weekly lvl15) is met and if the previously active quests
|
||||||
* are still valid. This is checked by endTime persisted in profile according to the resetTime configured for each repeatable kind (daily, weekly)
|
* are still valid. This ischecked by endTime persisted in profile accordning to the resetTime configured for each repeatable kind (daily, weekly)
|
||||||
* in QuestConfig.js
|
* in QuestCondig.js
|
||||||
*
|
*
|
||||||
* If the condition is met, new repeatableQuests are created, old quests (which are persisted in the profile.RepeatableQuests[i].activeQuests) are
|
* If the condition is met, new repeatableQuests are created, old quests (which are persisted in the profile.RepeatableQuests[i].activeQuests) are
|
||||||
* moved to profile.RepeatableQuests[i].inactiveQuests. This memory is required to get rid of old repeatable quest data in the profile, otherwise
|
* moved to profile.RepeatableQuests[i].inactiveQuests. This memory is required to get rid of old repeatable quest data in the profile, otherwise
|
||||||
@ -81,8 +81,10 @@ export class RepeatableQuestController
|
|||||||
* (if the are on "Succeed" but not "Completed" we keep them, to allow the player to complete them and get the rewards)
|
* (if the are on "Succeed" but not "Completed" we keep them, to allow the player to complete them and get the rewards)
|
||||||
* The new quests generated are again persisted in profile.RepeatableQuests
|
* The new quests generated are again persisted in profile.RepeatableQuests
|
||||||
*
|
*
|
||||||
* @param {string} sessionId Player's session id
|
* @param {string} _info Request from client
|
||||||
* @returns {array} array of "repeatableQuestObjects" as described above
|
* @param {string} sessionID Player's session id
|
||||||
|
*
|
||||||
|
* @returns {array} array of "repeatableQuestObjects" as descibed above
|
||||||
*/
|
*/
|
||||||
public getClientRepeatableQuests(_info: IEmptyRequestData, sessionID: string): IPmcDataRepeatableQuest[]
|
public getClientRepeatableQuests(_info: IEmptyRequestData, sessionID: string): IPmcDataRepeatableQuest[]
|
||||||
{
|
{
|
||||||
@ -322,7 +324,7 @@ export class RepeatableQuestController
|
|||||||
{
|
{
|
||||||
const possibleLocations = Object.keys(repeatableConfig.locations);
|
const possibleLocations = Object.keys(repeatableConfig.locations);
|
||||||
|
|
||||||
// Set possible locations for elimination task, if target is savage, exclude labs from locations
|
// Set possible locations for elimination task, ift arget is savage, exclude labs from locations
|
||||||
questPool.pool.Elimination.targets[probabilityObject.key] = (probabilityObject.key === "Savage")
|
questPool.pool.Elimination.targets[probabilityObject.key] = (probabilityObject.key === "Savage")
|
||||||
? { locations: possibleLocations.filter((x) => x !== "laboratory") }
|
? { locations: possibleLocations.filter((x) => x !== "laboratory") }
|
||||||
: { locations: possibleLocations };
|
: { locations: possibleLocations };
|
||||||
@ -397,8 +399,7 @@ export class RepeatableQuestController
|
|||||||
// Get cost to replace existing quest
|
// Get cost to replace existing quest
|
||||||
changeRequirement = this.jsonUtil.clone(currentRepeatablePool.changeRequirement[changeRequest.qid]);
|
changeRequirement = this.jsonUtil.clone(currentRepeatablePool.changeRequirement[changeRequest.qid]);
|
||||||
delete currentRepeatablePool.changeRequirement[changeRequest.qid];
|
delete currentRepeatablePool.changeRequirement[changeRequest.qid];
|
||||||
|
// TODO: somehow we need to reduce the questPool by the currently active quests (for all repeatables)
|
||||||
// TODO: somehow we need to reduce the questPool by the currently active quests (for all repeatable)
|
|
||||||
|
|
||||||
const repeatableConfig = this.questConfig.repeatableQuests.find((x) =>
|
const repeatableConfig = this.questConfig.repeatableQuests.find((x) =>
|
||||||
x.name === currentRepeatablePool.name
|
x.name === currentRepeatablePool.name
|
||||||
@ -492,6 +493,7 @@ export class RepeatableQuestController
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newRepeatableQuest;
|
return newRepeatableQuest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ export class LocationGenerator
|
|||||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||||
): SpawnpointTemplate[]
|
): SpawnpointTemplate[]
|
||||||
{
|
{
|
||||||
|
let staticLootItemCount = 0;
|
||||||
const result: SpawnpointTemplate[] = [];
|
const result: SpawnpointTemplate[] = [];
|
||||||
const locationId = locationBase.Id.toLowerCase();
|
const locationId = locationBase.Id.toLowerCase();
|
||||||
|
|
||||||
@ -127,11 +128,13 @@ export class LocationGenerator
|
|||||||
locationId,
|
locationId,
|
||||||
);
|
);
|
||||||
result.push(containerWithLoot.template);
|
result.push(containerWithLoot.template);
|
||||||
|
|
||||||
|
staticLootItemCount += containerWithLoot.template.Items.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.success(`Added ${guaranteedContainers.length} guaranteed containers`);
|
this.logger.debug(`Added ${guaranteedContainers.length} guaranteed containers`);
|
||||||
|
|
||||||
// randomisation is turned off globally or just turned off for this map
|
// Randomisation is turned off globally or just turned off for this map
|
||||||
if (
|
if (
|
||||||
!(this.locationConfig.containerRandomisationSettings.enabled
|
!(this.locationConfig.containerRandomisationSettings.enabled
|
||||||
&& this.locationConfig.containerRandomisationSettings.maps[locationId])
|
&& this.locationConfig.containerRandomisationSettings.maps[locationId])
|
||||||
@ -150,8 +153,12 @@ export class LocationGenerator
|
|||||||
locationId,
|
locationId,
|
||||||
);
|
);
|
||||||
result.push(containerWithLoot.template);
|
result.push(containerWithLoot.template);
|
||||||
|
|
||||||
|
staticLootItemCount += containerWithLoot.template.Items.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.success(`A total of ${staticLootItemCount} static items spawned`);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,9 +237,14 @@ export class LocationGenerator
|
|||||||
);
|
);
|
||||||
result.push(containerWithLoot.template);
|
result.push(containerWithLoot.template);
|
||||||
staticContainerCount++;
|
staticContainerCount++;
|
||||||
|
|
||||||
|
staticLootItemCount += containerWithLoot.template.Items.length;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.success(`A total of ${staticLootItemCount} static items spawned`);
|
||||||
|
|
||||||
this.logger.success(
|
this.logger.success(
|
||||||
this.localisationService.getText("location-containers_generated_success", staticContainerCount),
|
this.localisationService.getText("location-containers_generated_success", staticContainerCount),
|
||||||
);
|
);
|
||||||
|
@ -307,6 +307,8 @@ export class BotGeneratorHelper
|
|||||||
slot: equipmentSlot,
|
slot: equipmentSlot,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return { incompatible: true, reason: `item: ${tplToCheck} does not exist in the database` };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!itemToCheck._props)
|
if (!itemToCheck._props)
|
||||||
@ -318,6 +320,8 @@ export class BotGeneratorHelper
|
|||||||
slot: equipmentSlot,
|
slot: equipmentSlot,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return { incompatible: true, reason: `item: ${tplToCheck} does not have a _props field` };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does an equipped item have a property that blocks the desired item - check for prop "BlocksX" .e.g BlocksEarpiece / BlocksFaceCover
|
// Does an equipped item have a property that blocks the desired item - check for prop "BlocksX" .e.g BlocksEarpiece / BlocksFaceCover
|
||||||
|
@ -21,6 +21,7 @@ import { SaveServer } from "@spt-aki/servers/SaveServer";
|
|||||||
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||||
import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
|
import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
|
||||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||||
|
import { TimeUtil } from "@spt-aki/utils/TimeUtil";
|
||||||
import { ProfileHelper } from "./ProfileHelper";
|
import { ProfileHelper } from "./ProfileHelper";
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@ -31,6 +32,7 @@ export class InRaidHelper
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject("WinstonLogger") protected logger: ILogger,
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||||
@inject("SaveServer") protected saveServer: SaveServer,
|
@inject("SaveServer") protected saveServer: SaveServer,
|
||||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||||
@ -129,7 +131,6 @@ export class InRaidHelper
|
|||||||
* Reset a profile to a baseline, used post-raid
|
* Reset a profile to a baseline, used post-raid
|
||||||
* Reset points earned during session property
|
* Reset points earned during session property
|
||||||
* Increment exp
|
* Increment exp
|
||||||
* Remove Labs keycard
|
|
||||||
* @param profileData Profile to update
|
* @param profileData Profile to update
|
||||||
* @param saveProgressRequest post raid save data request data
|
* @param saveProgressRequest post raid save data request data
|
||||||
* @param sessionID Session id
|
* @param sessionID Session id
|
||||||
@ -218,7 +219,7 @@ export class InRaidHelper
|
|||||||
public updatePmcProfileDataPostRaid(pmcData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionId: string): void
|
public updatePmcProfileDataPostRaid(pmcData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionId: string): void
|
||||||
{
|
{
|
||||||
// Process failed quests then copy everything
|
// Process failed quests then copy everything
|
||||||
this.processFailedQuests(sessionId, pmcData, pmcData.Quests, saveProgressRequest.profile);
|
this.processAlteredQuests(sessionId, pmcData, pmcData.Quests, saveProgressRequest.profile);
|
||||||
pmcData.Quests = saveProgressRequest.profile.Quests;
|
pmcData.Quests = saveProgressRequest.profile.Quests;
|
||||||
|
|
||||||
// No need to do this for scav, old scav is deleted and new one generated
|
// No need to do this for scav, old scav is deleted and new one generated
|
||||||
@ -250,19 +251,20 @@ export class InRaidHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look for quests with status = fail that were not failed pre-raid and run the failQuest() function
|
* Look for quests with a status different from what it began the raid with
|
||||||
* @param sessionId Player id
|
* @param sessionId Player id
|
||||||
* @param pmcData Player profile
|
* @param pmcData Player profile
|
||||||
* @param preRaidQuests Quests prior to starting raid
|
* @param preRaidQuests Quests prior to starting raid
|
||||||
* @param postRaidProfile Profile sent by client
|
* @param postRaidProfile Profile sent by client with post-raid quests
|
||||||
*/
|
*/
|
||||||
protected processFailedQuests(
|
protected processAlteredQuests(
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
pmcData: IPmcData,
|
pmcData: IPmcData,
|
||||||
preRaidQuests: IQuestStatus[],
|
preRaidQuests: IQuestStatus[],
|
||||||
postRaidProfile: IPostRaidPmcData,
|
postRaidProfile: IPostRaidPmcData,
|
||||||
): void
|
): void
|
||||||
{
|
{
|
||||||
|
// TODO: this may break when locked quests are added to profile but player has completed no quests prior to raid
|
||||||
if (!preRaidQuests)
|
if (!preRaidQuests)
|
||||||
{
|
{
|
||||||
// No quests to compare against, skip
|
// No quests to compare against, skip
|
||||||
@ -270,29 +272,45 @@ export class InRaidHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loop over all quests from post-raid profile
|
// Loop over all quests from post-raid profile
|
||||||
|
const newLockedQuests: IQuestStatus[] = [];
|
||||||
for (const postRaidQuest of postRaidProfile.Quests)
|
for (const postRaidQuest of postRaidProfile.Quests)
|
||||||
{
|
{
|
||||||
// Find matching pre-raid quest + not already failed
|
// postRaidQuest.status has a weird value, need to do some nasty casting to compare it
|
||||||
|
const postRaidQuestStatus = <string><unknown>postRaidQuest.status;
|
||||||
|
|
||||||
|
// Find matching pre-raid quest
|
||||||
const preRaidQuest = preRaidQuests?.find((x) => x.qid === postRaidQuest.qid);
|
const preRaidQuest = preRaidQuests?.find((x) => x.qid === postRaidQuest.qid);
|
||||||
if (!preRaidQuest)
|
if (!preRaidQuest)
|
||||||
{
|
{
|
||||||
|
// Some traders gives locked quests (LightKeeper) due to time-gating
|
||||||
|
if (postRaidQuestStatus === "Locked")
|
||||||
|
{
|
||||||
|
// Store new locked quest for future processing
|
||||||
|
newLockedQuests.push(postRaidQuest);
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Already failed before raid, skip
|
// Already completed/failed before raid, skip
|
||||||
if (preRaidQuest.status === QuestStatus.Fail)
|
if ([QuestStatus.Fail, QuestStatus.Success].includes(preRaidQuest.status) )
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preRaidQuest.status === QuestStatus.Success)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quest failed inside raid, need to handle
|
// Quest with time-gate has unlocked
|
||||||
// postRaidQuest.status has a weird value, need to do some nasty casting to compare it
|
if (postRaidQuestStatus === "AvailableAfter" && postRaidQuest.availableAfter <= this.timeUtil.getTimestamp())
|
||||||
const postRaidQuestStatus = <string><unknown>postRaidQuest.status;
|
{
|
||||||
|
// Flag as ready to complete
|
||||||
|
postRaidQuest.status = QuestStatus.AvailableForStart;
|
||||||
|
postRaidQuest.statusTimers[QuestStatus.AvailableForStart] = this.timeUtil.getTimestamp();
|
||||||
|
|
||||||
|
this.logger.debug(`Time-locked quest ${postRaidQuest.qid} is now ready to start`);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quest failed inside raid
|
||||||
if (postRaidQuestStatus === "Fail")
|
if (postRaidQuestStatus === "Fail")
|
||||||
{
|
{
|
||||||
// Send failed message
|
// Send failed message
|
||||||
@ -347,6 +365,34 @@ export class InRaidHelper
|
|||||||
postRaidQuest.completedConditions = [];
|
postRaidQuest.completedConditions = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reclassify time-gated quests as time gated until a specific date
|
||||||
|
if (newLockedQuests.length > 0)
|
||||||
|
{
|
||||||
|
for (const lockedQuest of newLockedQuests)
|
||||||
|
{
|
||||||
|
// Get the quest from Db
|
||||||
|
const dbQuest = this.questHelper.getQuestFromDb(lockedQuest.qid, null);
|
||||||
|
if (!dbQuest)
|
||||||
|
{
|
||||||
|
this.logger.warning(`Unable to adjust locked quest: ${lockedQuest.qid} as it wasnt found in db. It may not become available later on`);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the time requirement in AvailableForStart array (assuming there is one as quest in locked state === its time-gated)
|
||||||
|
const afsRequirement = dbQuest.conditions.AvailableForStart.find(x => x._parent === "Quest");
|
||||||
|
if (afsRequirement && afsRequirement._props.availableAfter > 0)
|
||||||
|
{
|
||||||
|
// Prereq quest has a wait
|
||||||
|
// Set quest as AvailableAfter and set timer
|
||||||
|
const timestamp = this.timeUtil.getTimestamp() + afsRequirement._props.availableAfter;
|
||||||
|
lockedQuest.availableAfter = timestamp;
|
||||||
|
lockedQuest.statusTimers.AvailableAfter = timestamp;
|
||||||
|
lockedQuest.status = 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -447,10 +493,10 @@ export class InRaidHelper
|
|||||||
&& this.itemHelper.itemIsInsideContainer(x, "SecuredContainer", postRaidProfile.Inventory.items));
|
&& this.itemHelper.itemIsInsideContainer(x, "SecuredContainer", postRaidProfile.Inventory.items));
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const item of itemsToRemovePropertyFrom)
|
itemsToRemovePropertyFrom.forEach((item) =>
|
||||||
{
|
{
|
||||||
delete item.upd.SpawnedInSession;
|
delete item.upd.SpawnedInSession;
|
||||||
}
|
});
|
||||||
|
|
||||||
return postRaidProfile;
|
return postRaidProfile;
|
||||||
}
|
}
|
||||||
@ -493,10 +539,11 @@ export class InRaidHelper
|
|||||||
{
|
{
|
||||||
// Get inventory item ids to remove from players profile
|
// Get inventory item ids to remove from players profile
|
||||||
const itemIdsToDeleteFromProfile = this.getInventoryItemsLostOnDeath(pmcData).map((x) => x._id);
|
const itemIdsToDeleteFromProfile = this.getInventoryItemsLostOnDeath(pmcData).map((x) => x._id);
|
||||||
for (const itemId of itemIdsToDeleteFromProfile)
|
itemIdsToDeleteFromProfile.forEach((x) =>
|
||||||
{
|
{
|
||||||
this.inventoryHelper.removeItem(pmcData, itemId, sessionID);
|
// Items inside containers are handed as part of function
|
||||||
}
|
this.inventoryHelper.removeItem(pmcData, x, sessionID);
|
||||||
|
});
|
||||||
|
|
||||||
// Remove contents of fast panel
|
// Remove contents of fast panel
|
||||||
pmcData.Inventory.fastPanel = {};
|
pmcData.Inventory.fastPanel = {};
|
||||||
@ -510,25 +557,25 @@ export class InRaidHelper
|
|||||||
protected getInventoryItemsLostOnDeath(pmcProfile: IPmcData): Item[]
|
protected getInventoryItemsLostOnDeath(pmcProfile: IPmcData): Item[]
|
||||||
{
|
{
|
||||||
const inventoryItems = pmcProfile.Inventory.items ?? [];
|
const inventoryItems = pmcProfile.Inventory.items ?? [];
|
||||||
const equipment = pmcProfile?.Inventory?.equipment;
|
const equipmentRootId = pmcProfile?.Inventory?.equipment;
|
||||||
const questRaidItems = pmcProfile?.Inventory?.questRaidItems;
|
const questRaidItemContainerId = pmcProfile?.Inventory?.questRaidItems;
|
||||||
|
|
||||||
return inventoryItems.filter((x) =>
|
return inventoryItems.filter((item) =>
|
||||||
{
|
{
|
||||||
// Keep items flagged as kept after death
|
// Keep items flagged as kept after death
|
||||||
if (this.isItemKeptAfterDeath(pmcProfile, x))
|
if (this.isItemKeptAfterDeath(pmcProfile, item))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove normal items or quest raid items
|
// Remove normal items or quest raid items
|
||||||
if (x.parentId === equipment || x.parentId === questRaidItems)
|
if (item.parentId === equipmentRootId || item.parentId === questRaidItemContainerId)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pocket items are not lost on death
|
// Pocket items are lost on death
|
||||||
if (x.slotId.startsWith("pocket"))
|
if (item.slotId.startsWith("pocket"))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -563,7 +610,7 @@ export class InRaidHelper
|
|||||||
*/
|
*/
|
||||||
protected isItemKeptAfterDeath(pmcData: IPmcData, itemToCheck: Item): boolean
|
protected isItemKeptAfterDeath(pmcData: IPmcData, itemToCheck: Item): boolean
|
||||||
{
|
{
|
||||||
// No parentid means its a base inventory item, always keep
|
// No parentId = base inventory item, always keep
|
||||||
if (!itemToCheck.parentId)
|
if (!itemToCheck.parentId)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
@ -583,7 +630,7 @@ export class InRaidHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Is quest item + quest item not lost on death
|
// Is quest item + quest item not lost on death
|
||||||
if (!this.lostOnDeathConfig.questItems && itemToCheck.parentId === pmcData.Inventory.questRaidItems)
|
if (itemToCheck.parentId === pmcData.Inventory.questRaidItems && !this.lostOnDeathConfig.questItems)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -623,6 +670,9 @@ export class InRaidHelper
|
|||||||
"pocket2",
|
"pocket2",
|
||||||
"pocket3",
|
"pocket3",
|
||||||
"pocket4",
|
"pocket4",
|
||||||
|
"SpecialSlot1",
|
||||||
|
"SpecialSlot2",
|
||||||
|
"SpecialSlot3",
|
||||||
];
|
];
|
||||||
|
|
||||||
let inventoryItems: Item[] = [];
|
let inventoryItems: Item[] = [];
|
||||||
|
@ -402,7 +402,7 @@ export class ProfileHelper
|
|||||||
{
|
{
|
||||||
if (!pointsToAdd || pointsToAdd < 0)
|
if (!pointsToAdd || pointsToAdd < 0)
|
||||||
{
|
{
|
||||||
this.logger.error(
|
this.logger.warning(
|
||||||
this.localisationService.getText("player-attempt_to_increment_skill_with_negative_value", skill),
|
this.localisationService.getText("player-attempt_to_increment_skill_with_negative_value", skill),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -432,6 +432,7 @@ export class ProfileHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
profileSkill.Progress += pointsToAdd;
|
profileSkill.Progress += pointsToAdd;
|
||||||
|
profileSkill.Progress = Math.min(profileSkill.Progress, 5100); // Prevent skill from ever going above level 51 (5100)
|
||||||
profileSkill.LastAccess = this.timeUtil.getTimestamp();
|
profileSkill.LastAccess = this.timeUtil.getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import { TraderAssortHelper } from "@spt-aki/helpers/TraderAssortHelper";
|
|||||||
import { UtilityHelper } from "@spt-aki/helpers/UtilityHelper";
|
import { UtilityHelper } from "@spt-aki/helpers/UtilityHelper";
|
||||||
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
||||||
import { ITraderAssort } from "@spt-aki/models/eft/common/tables/ITrader";
|
import { ITraderAssort } from "@spt-aki/models/eft/common/tables/ITrader";
|
||||||
import { IGetOffersResult } from "@spt-aki/models/eft/ragfair/IGetOffersResult";
|
|
||||||
import { ISearchRequestData } from "@spt-aki/models/eft/ragfair/ISearchRequestData";
|
import { ISearchRequestData } from "@spt-aki/models/eft/ragfair/ISearchRequestData";
|
||||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||||
import { Money } from "@spt-aki/models/enums/Money";
|
import { Money } from "@spt-aki/models/enums/Money";
|
||||||
@ -59,27 +58,27 @@ export class RagfairHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public filterCategories(sessionID: string, info: ISearchRequestData): string[]
|
public filterCategories(sessionID: string, request: ISearchRequestData): string[]
|
||||||
{
|
{
|
||||||
let result: string[] = [];
|
let result: string[] = [];
|
||||||
|
|
||||||
// Case: weapon builds
|
// Case: weapon builds
|
||||||
if (info.buildCount)
|
if (request.buildCount)
|
||||||
{
|
{
|
||||||
return Object.keys(info.buildItems);
|
return Object.keys(request.buildItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case: search
|
// Case: search
|
||||||
if (info.linkedSearchId)
|
if (request.linkedSearchId)
|
||||||
{
|
{
|
||||||
const data = this.ragfairLinkedItemService.getLinkedItems(info.linkedSearchId);
|
const data = this.ragfairLinkedItemService.getLinkedItems(request.linkedSearchId);
|
||||||
result = !data ? [] : [...data];
|
result = !data ? [] : [...data];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case: category
|
// Case: category
|
||||||
if (info.handbookId)
|
if (request.handbookId)
|
||||||
{
|
{
|
||||||
const handbook = this.getCategoryList(info.handbookId);
|
const handbook = this.getCategoryList(request.handbookId);
|
||||||
|
|
||||||
if (result.length)
|
if (result.length)
|
||||||
{
|
{
|
||||||
@ -182,6 +181,12 @@ export class RagfairHelper
|
|||||||
return [...[rootItem], ...list];
|
return [...[rootItem], ...list];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the symbol for a currency
|
||||||
|
* e.g. 5449016a4bdc2d6f028b456f return ₽
|
||||||
|
* @param currencyTpl currency to get symbol for
|
||||||
|
* @returns symbol of currency
|
||||||
|
*/
|
||||||
public getCurrencySymbol(currencyTpl: string): string
|
public getCurrencySymbol(currencyTpl: string): string
|
||||||
{
|
{
|
||||||
switch (currencyTpl)
|
switch (currencyTpl)
|
||||||
|
@ -354,6 +354,7 @@ export class RagfairOfferHelper
|
|||||||
const profileRagfairInfo = this.saveServer.getProfile(sessionID).characters.pmc.RagfairInfo;
|
const profileRagfairInfo = this.saveServer.getProfile(sessionID).characters.pmc.RagfairInfo;
|
||||||
const index = profileRagfairInfo.offers.findIndex((o) => o._id === offerId);
|
const index = profileRagfairInfo.offers.findIndex((o) => o._id === offerId);
|
||||||
profileRagfairInfo.offers.splice(index, 1);
|
profileRagfairInfo.offers.splice(index, 1);
|
||||||
|
|
||||||
// Also delete from ragfair
|
// Also delete from ragfair
|
||||||
this.ragfairOfferService.removeOfferById(offerId);
|
this.ragfairOfferService.removeOfferById(offerId);
|
||||||
}
|
}
|
||||||
@ -436,15 +437,14 @@ export class RagfairOfferHelper
|
|||||||
};
|
};
|
||||||
|
|
||||||
const stacks = this.itemHelper.splitStack(requestedItem);
|
const stacks = this.itemHelper.splitStack(requestedItem);
|
||||||
|
|
||||||
for (const item of stacks)
|
for (const item of stacks)
|
||||||
{
|
{
|
||||||
const outItems = [item];
|
const outItems = [item];
|
||||||
|
|
||||||
|
// TODO - is this code used?, may have been when adding barters to flea was still possible for player
|
||||||
if (requirement.onlyFunctional)
|
if (requirement.onlyFunctional)
|
||||||
{
|
{
|
||||||
const presetItems = this.ragfairServerHelper.getPresetItemsByTpl(item);
|
const presetItems = this.ragfairServerHelper.getPresetItemsByTpl(item);
|
||||||
|
|
||||||
if (presetItems.length)
|
if (presetItems.length)
|
||||||
{
|
{
|
||||||
outItems.push(presetItems[0]);
|
outItems.push(presetItems[0]);
|
||||||
|
@ -13,6 +13,7 @@ import { MessageType } from "@spt-aki/models/enums/MessageType";
|
|||||||
import { Traders } from "@spt-aki/models/enums/Traders";
|
import { Traders } from "@spt-aki/models/enums/Traders";
|
||||||
import { IQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig";
|
import { IQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig";
|
||||||
import { IRagfairConfig } from "@spt-aki/models/spt/config/IRagfairConfig";
|
import { IRagfairConfig } from "@spt-aki/models/spt/config/IRagfairConfig";
|
||||||
|
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";
|
||||||
@ -35,6 +36,7 @@ export class RagfairServerHelper
|
|||||||
protected static goodsReturnedTemplate = "5bdabfe486f7743e1665df6e 0"; // Your item was not sold
|
protected static goodsReturnedTemplate = "5bdabfe486f7743e1665df6e 0"; // Your item was not sold
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||||
@ -236,6 +238,11 @@ export class RagfairServerHelper
|
|||||||
return MemberCategory.DEFAULT;
|
return MemberCategory.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a player or traders nickname from their profile by their user id
|
||||||
|
* @param userID Sessionid/userid
|
||||||
|
* @returns Nickname of individual
|
||||||
|
*/
|
||||||
public getNickname(userID: string): string
|
public getNickname(userID: string): string
|
||||||
{
|
{
|
||||||
if (this.isPlayer(userID))
|
if (this.isPlayer(userID))
|
||||||
@ -257,16 +264,25 @@ export class RagfairServerHelper
|
|||||||
return (name.length > 15) ? this.getNickname(userID) : name;
|
return (name.length > 15) ? this.getNickname(userID) : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPresetItems(item: any): Item[]
|
/**
|
||||||
|
* Given a preset id from globals.json, return an array of items[] with unique ids
|
||||||
|
* @param item Preset item
|
||||||
|
* @returns Array of weapon and its children
|
||||||
|
*/
|
||||||
|
public getPresetItems(item: Item): Item[]
|
||||||
{
|
{
|
||||||
const preset = this.jsonUtil.clone(this.databaseServer.getTables().globals.ItemPresets[item._id]._items);
|
const preset = this.jsonUtil.clone(this.databaseServer.getTables().globals.ItemPresets[item._id]._items);
|
||||||
return this.reparentPresets(item, preset);
|
return this.reparentPresets(item, preset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possible bug, returns all items associated with an items tpl, could be multiple presets from globals.json
|
||||||
|
* @param item Preset item
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
public getPresetItemsByTpl(item: Item): Item[]
|
public getPresetItemsByTpl(item: Item): Item[]
|
||||||
{
|
{
|
||||||
const presets = [];
|
const presets = [];
|
||||||
|
|
||||||
for (const itemId in this.databaseServer.getTables().globals.ItemPresets)
|
for (const itemId in this.databaseServer.getTables().globals.ItemPresets)
|
||||||
{
|
{
|
||||||
if (this.databaseServer.getTables().globals.ItemPresets[itemId]._items[0]._tpl === item._tpl)
|
if (this.databaseServer.getTables().globals.ItemPresets[itemId]._items[0]._tpl === item._tpl)
|
||||||
@ -282,17 +298,17 @@ export class RagfairServerHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate new unique ids for the children while preserving hierarchy
|
* Generate new unique ids for child items while preserving hierarchy
|
||||||
* @param item base item
|
* @param rootItem Base/primary item of preset
|
||||||
* @param preset
|
* @param preset Primary item + children of primary item
|
||||||
* @returns Item array with new IDs
|
* @returns Item array with new IDs
|
||||||
*/
|
*/
|
||||||
public reparentPresets(item: Item, preset: Item[]): Item[]
|
public reparentPresets(rootItem: Item, preset: Item[]): Item[]
|
||||||
{
|
{
|
||||||
const oldRootId = preset[0]._id;
|
const oldRootId = preset[0]._id;
|
||||||
const idMappings = {};
|
const idMappings = {};
|
||||||
|
|
||||||
idMappings[oldRootId] = item._id;
|
idMappings[oldRootId] = rootItem._id;
|
||||||
|
|
||||||
for (const mod of preset)
|
for (const mod of preset)
|
||||||
{
|
{
|
||||||
@ -314,8 +330,13 @@ export class RagfairServerHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// force item's details into first location of presetItems
|
// Force item's details into first location of presetItems
|
||||||
preset[0] = item;
|
if (preset[0]._tpl !== rootItem._tpl)
|
||||||
|
{
|
||||||
|
this.logger.warning(`Reassigning root item from ${preset[0]._tpl} to ${rootItem._tpl}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
preset[0] = rootItem;
|
||||||
|
|
||||||
return preset;
|
return preset;
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ export class RepairHelper
|
|||||||
): void
|
): void
|
||||||
{
|
{
|
||||||
this.logger.debug(`Adding ${amountToRepair} to ${itemToRepairDetails._name} using kit: ${useRepairKit}`);
|
this.logger.debug(`Adding ${amountToRepair} to ${itemToRepairDetails._name} using kit: ${useRepairKit}`);
|
||||||
|
|
||||||
const itemMaxDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.MaxDurability);
|
const itemMaxDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.MaxDurability);
|
||||||
const itemCurrentDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.Durability);
|
const itemCurrentDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.Durability);
|
||||||
const itemCurrentMaxDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.MaxDurability);
|
const itemCurrentMaxDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.MaxDurability);
|
||||||
@ -105,6 +106,14 @@ export class RepairHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repairing armor reduces the total durability value slightly, get a randomised (to 2dp) amount based on armor material
|
||||||
|
* @param armorMaterial What material is the armor being repaired made of
|
||||||
|
* @param isRepairKit Was a repair kit used
|
||||||
|
* @param armorMax Max amount of durability item can have
|
||||||
|
* @param traderQualityMultipler Different traders produce different loss values
|
||||||
|
* @returns Amount to reduce max durability by
|
||||||
|
*/
|
||||||
protected getRandomisedArmorRepairDegradationValue(
|
protected getRandomisedArmorRepairDegradationValue(
|
||||||
armorMaterial: string,
|
armorMaterial: string,
|
||||||
isRepairKit: boolean,
|
isRepairKit: boolean,
|
||||||
@ -112,6 +121,7 @@ export class RepairHelper
|
|||||||
traderQualityMultipler: number,
|
traderQualityMultipler: number,
|
||||||
): number
|
): number
|
||||||
{
|
{
|
||||||
|
// Degradation value is based on the armor material
|
||||||
const armorMaterialSettings = this.databaseServer.getTables().globals.config.ArmorMaterials[armorMaterial];
|
const armorMaterialSettings = this.databaseServer.getTables().globals.config.ArmorMaterials[armorMaterial];
|
||||||
|
|
||||||
const minMultiplier = isRepairKit
|
const minMultiplier = isRepairKit
|
||||||
@ -128,6 +138,14 @@ export class RepairHelper
|
|||||||
return Number(duraLossMultipliedByTraderMultiplier.toFixed(2));
|
return Number(duraLossMultipliedByTraderMultiplier.toFixed(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repairing weapons reduces the total durability value slightly, get a randomised (to 2dp) amount
|
||||||
|
* @param itemProps Weapon properties
|
||||||
|
* @param isRepairKit Was a repair kit used
|
||||||
|
* @param weaponMax ax amount of durability item can have
|
||||||
|
* @param traderQualityMultipler Different traders produce different loss values
|
||||||
|
* @returns Amount to reduce max durability by
|
||||||
|
*/
|
||||||
protected getRandomisedWeaponRepairDegradationValue(
|
protected getRandomisedWeaponRepairDegradationValue(
|
||||||
itemProps: Props,
|
itemProps: Props,
|
||||||
isRepairKit: boolean,
|
isRepairKit: boolean,
|
||||||
@ -149,19 +167,4 @@ export class RepairHelper
|
|||||||
|
|
||||||
return Number(duraLossMultipliedByTraderMultiplier.toFixed(2));
|
return Number(duraLossMultipliedByTraderMultiplier.toFixed(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the supplied tpl a weapon
|
|
||||||
* @param tpl tplId to check is a weapon
|
|
||||||
* @returns true if tpl is a weapon
|
|
||||||
*/
|
|
||||||
public isWeaponTemplate(tpl: string): boolean
|
|
||||||
{
|
|
||||||
const itemTemplates = this.databaseServer.getTables().templates.items;
|
|
||||||
const baseItem = itemTemplates[tpl];
|
|
||||||
const baseNode = itemTemplates[baseItem._parent];
|
|
||||||
const parentNode = itemTemplates[baseNode._parent];
|
|
||||||
|
|
||||||
return parentNode._id === BaseClasses.WEAPON;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,11 @@ export class SecureContainerHelper
|
|||||||
constructor(@inject("ItemHelper") protected itemHelper: ItemHelper)
|
constructor(@inject("ItemHelper") protected itemHelper: ItemHelper)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array of the item IDs (NOT tpls) inside a secure container
|
||||||
|
* @param items Inventory items to look for secure container in
|
||||||
|
* @returns Array of ids
|
||||||
|
*/
|
||||||
public getSecureContainerItems(items: Item[]): string[]
|
public getSecureContainerItems(items: Item[]): string[]
|
||||||
{
|
{
|
||||||
const secureContainer = items.find((x) => x.slotId === "SecuredContainer");
|
const secureContainer = items.find((x) => x.slotId === "SecuredContainer");
|
||||||
|
@ -120,7 +120,7 @@ export class TradeHelper
|
|||||||
this.incrementAssortBuyCount(itemPurchased, buyRequestData.count);
|
this.incrementAssortBuyCount(itemPurchased, buyRequestData.count);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug(`Bought item: ${buyRequestData.item_id} from ${buyRequestData.tid}`);
|
this.logger.debug(`Bought item: ${buyRequestData.item_id} from: ${Traders[buyRequestData.tid]}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, foundInRaid, upd);
|
return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, foundInRaid, upd);
|
||||||
@ -184,12 +184,18 @@ export class TradeHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traders allow a limited number of purchases per refresh cycle (default 60 mins)
|
||||||
|
* @param assortBeingPurchased the item from trader being bought
|
||||||
|
* @param assortId Id of assort being purchased
|
||||||
|
* @param count How many are being bought
|
||||||
|
*/
|
||||||
protected checkPurchaseIsWithinTraderItemLimit(assortBeingPurchased: Item, assortId: string, count: number): void
|
protected checkPurchaseIsWithinTraderItemLimit(assortBeingPurchased: Item, assortId: string, count: number): void
|
||||||
{
|
{
|
||||||
if ((assortBeingPurchased.upd.BuyRestrictionCurrent + count) > assortBeingPurchased.upd?.BuyRestrictionMax)
|
if ((assortBeingPurchased.upd.BuyRestrictionCurrent + count) > assortBeingPurchased.upd?.BuyRestrictionMax)
|
||||||
{
|
{
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to purchase ${count} items, this would exceed your purchase limit of ${assortBeingPurchased.upd.BuyRestrictionMax} from the trader this refresh`,
|
`Unable to purchase ${count} items, this would exceed your purchase limit of ${assortBeingPurchased.upd.BuyRestrictionMax} from the traders assort: ${assortId} this refresh`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,12 @@ export class TraderHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the starting standing of a trader based on the current profiles type (e.g. EoD, Standard etc)
|
||||||
|
* @param traderId Trader id to get standing for
|
||||||
|
* @param rawProfileTemplate Raw profile from profiles.json to look up standing from
|
||||||
|
* @returns Standing value
|
||||||
|
*/
|
||||||
protected getStartingStanding(traderId: string, rawProfileTemplate: ProfileTraderTemplate): number
|
protected getStartingStanding(traderId: string, rawProfileTemplate: ProfileTraderTemplate): number
|
||||||
{
|
{
|
||||||
// Edge case for Lightkeeper, 0 standing means seeing `Make Amends - Buyout` quest
|
// Edge case for Lightkeeper, 0 standing means seeing `Make Amends - Buyout` quest
|
||||||
|
@ -18,6 +18,11 @@ export class WeightedRandomHelper
|
|||||||
return chosenItem.item;
|
return chosenItem.item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choos an item from the passed in array based on the weightings of each
|
||||||
|
* @param itemArray Items and weights to use
|
||||||
|
* @returns Chosen item from array
|
||||||
|
*/
|
||||||
public getWeightedValue<T>(itemArray: { [key: string]: unknown; } | ArrayLike<unknown>): T
|
public getWeightedValue<T>(itemArray: { [key: string]: unknown; } | ArrayLike<unknown>): T
|
||||||
{
|
{
|
||||||
const itemKeys = Object.keys(itemArray);
|
const itemKeys = Object.keys(itemArray);
|
||||||
|
@ -146,10 +146,10 @@ export class PreAkiModLoader implements IModLoader
|
|||||||
const modOrder = this.vfs.readFile(this.modOrderPath, { encoding: "utf8" });
|
const modOrder = this.vfs.readFile(this.modOrderPath, { encoding: "utf8" });
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (const [index, mod] of (this.jsonUtil.deserialize<any>(modOrder).order as string[]).entries())
|
this.jsonUtil.deserialize<any>(modOrder, this.modOrderPath).order.forEach((mod: string, index: number) =>
|
||||||
{
|
{
|
||||||
this.order[mod] = index;
|
this.order[mod] = index;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
catch (error)
|
catch (error)
|
||||||
{
|
{
|
||||||
@ -213,10 +213,11 @@ export class PreAkiModLoader implements IModLoader
|
|||||||
validMods.sort((prev, next) => this.sortMods(prev, next, missingFromOrderJSON));
|
validMods.sort((prev, next) => this.sortMods(prev, next, missingFromOrderJSON));
|
||||||
|
|
||||||
// log the missing mods from order.json
|
// log the missing mods from order.json
|
||||||
for (const missingMod of Object.keys(missingFromOrderJSON))
|
Object.keys(missingFromOrderJSON).forEach((
|
||||||
{
|
missingMod,
|
||||||
this.logger.debug(this.localisationService.getText("modloader-mod_order_missing_from_json", missingMod));
|
) => (this.logger.debug(
|
||||||
}
|
this.localisationService.getText("modloader-mod_order_missing_from_json", missingMod),
|
||||||
|
)));
|
||||||
|
|
||||||
// add mods
|
// add mods
|
||||||
for (const mod of validMods)
|
for (const mod of validMods)
|
||||||
@ -428,9 +429,10 @@ export class PreAkiModLoader implements IModLoader
|
|||||||
public sortModsLoadOrder(): string[]
|
public sortModsLoadOrder(): string[]
|
||||||
{
|
{
|
||||||
// if loadorder.json exists: load it, otherwise generate load order
|
// if loadorder.json exists: load it, otherwise generate load order
|
||||||
if (this.vfs.exists(`${this.basepath}loadorder.json`))
|
const loadOrderPath = `${this.basepath}loadorder.json`
|
||||||
|
if (this.vfs.exists(loadOrderPath))
|
||||||
{
|
{
|
||||||
return this.jsonUtil.deserialize(this.vfs.readFile(`${this.basepath}loadorder.json`));
|
return this.jsonUtil.deserialize(this.vfs.readFile(loadOrderPath), loadOrderPath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -660,14 +662,15 @@ export class PreAkiModLoader implements IModLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if config exists
|
// Check if config exists
|
||||||
if (!this.vfs.exists(`${modPath}/package.json`))
|
const modPackagePath = `${modPath}/package.json`;
|
||||||
|
if (!this.vfs.exists(modPackagePath))
|
||||||
{
|
{
|
||||||
this.logger.error(this.localisationService.getText("modloader-missing_package_json", modName));
|
this.logger.error(this.localisationService.getText("modloader-missing_package_json", modName));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate mod
|
// Validate mod
|
||||||
const config = this.jsonUtil.deserialize<IPackageJsonData>(this.vfs.readFile(`${modPath}/package.json`));
|
const config = this.jsonUtil.deserialize<IPackageJsonData>(this.vfs.readFile(modPackagePath), modPackagePath);
|
||||||
const checks = ["name", "author", "version", "license"];
|
const checks = ["name", "author", "version", "license"];
|
||||||
let issue = false;
|
let issue = false;
|
||||||
|
|
||||||
|
@ -196,14 +196,14 @@ export class EventOutputHolder
|
|||||||
for (const productionKey in productions)
|
for (const productionKey in productions)
|
||||||
{
|
{
|
||||||
const production = productions[productionKey];
|
const production = productions[productionKey];
|
||||||
if (production.sptIsComplete && production.sptIsContinuous)
|
if (production?.sptIsComplete && production?.sptIsContinuous)
|
||||||
{
|
{
|
||||||
// Water collector / Bitcoin etc
|
// Water collector / Bitcoin etc
|
||||||
production.sptIsComplete = false;
|
production.sptIsComplete = false;
|
||||||
production.Progress = 0;
|
production.Progress = 0;
|
||||||
production.StartTimestamp = this.timeUtil.getTimestamp();
|
production.StartTimestamp = this.timeUtil.getTimestamp();
|
||||||
}
|
}
|
||||||
else if (!production.inProgress)
|
else if (!production?.inProgress)
|
||||||
{
|
{
|
||||||
// Normal completed craft, delete
|
// Normal completed craft, delete
|
||||||
delete productions[productionKey];
|
delete productions[productionKey];
|
||||||
|
@ -11,6 +11,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
|||||||
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||||
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
||||||
|
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class WebSocketServer
|
export class WebSocketServer
|
||||||
@ -22,6 +23,7 @@ export class WebSocketServer
|
|||||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||||
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
|
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
|
||||||
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
||||||
@ -92,11 +94,13 @@ export class WebSocketServer
|
|||||||
// Strip request and break it into sections
|
// Strip request and break it into sections
|
||||||
const splitUrl = req.url.substring(0, req.url.indexOf("?")).split("/");
|
const splitUrl = req.url.substring(0, req.url.indexOf("?")).split("/");
|
||||||
const sessionID = splitUrl.pop();
|
const sessionID = splitUrl.pop();
|
||||||
|
const playerProfile = this.profileHelper.getFullProfile(sessionID);
|
||||||
|
const playerInfoText = `${playerProfile.info.username} (${sessionID})`;
|
||||||
|
|
||||||
this.logger.info(this.localisationService.getText("websocket-player_connected", sessionID));
|
this.logger.info(this.localisationService.getText("websocket-player_connected", playerInfoText));
|
||||||
|
|
||||||
const logger = this.logger;
|
const logger = this.logger;
|
||||||
const msgToLog = this.localisationService.getText("websocket-received_message", sessionID);
|
const msgToLog = this.localisationService.getText("websocket-received_message", playerInfoText);
|
||||||
ws.on("message", function message(msg)
|
ws.on("message", function message(msg)
|
||||||
{
|
{
|
||||||
logger.info(`${msgToLog} ${msg}`);
|
logger.info(`${msgToLog} ${msg}`);
|
||||||
|
@ -27,7 +27,7 @@ export class HashCacheService
|
|||||||
// get mod hash file
|
// get mod hash file
|
||||||
if (!this.modHashes)
|
if (!this.modHashes)
|
||||||
{ // empty
|
{ // empty
|
||||||
this.modHashes = this.jsonUtil.deserialize(this.vfs.readFile(`${this.modCachePath}`));
|
this.modHashes = this.jsonUtil.deserialize(this.vfs.readFile(`${this.modCachePath}`), this.modCachePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -524,7 +524,7 @@ export class ProfileFixerService
|
|||||||
{
|
{
|
||||||
// Old profiles had quests with a bad status of 0 (invalid) added to profile, remove them
|
// Old profiles had quests with a bad status of 0 (invalid) added to profile, remove them
|
||||||
// E.g. compensation for damage showing before standing check was added to getClientQuests()
|
// E.g. compensation for damage showing before standing check was added to getClientQuests()
|
||||||
if (quest.status === 0 && !isDevProfile)
|
if (quest.status === 0 && quest.availableAfter === 0 && !isDevProfile)
|
||||||
{
|
{
|
||||||
questsToDelete.push(quest);
|
questsToDelete.push(quest);
|
||||||
|
|
||||||
|
@ -129,6 +129,7 @@ export class RaidTimeAdjustmentService
|
|||||||
const chosenRaidReductionPercent = Number.parseInt(this.weightedRandomHelper.getWeightedValue<string>(
|
const chosenRaidReductionPercent = Number.parseInt(this.weightedRandomHelper.getWeightedValue<string>(
|
||||||
mapSettings.reductionPercentWeights,
|
mapSettings.reductionPercentWeights,
|
||||||
));
|
));
|
||||||
|
const raidTimeRemainingPercent = 100 - chosenRaidReductionPercent;
|
||||||
|
|
||||||
// How many minutes raid will last
|
// How many minutes raid will last
|
||||||
const newRaidTimeMinutes = Math.floor(this.randomUtil.reduceValueByPercent(baseEscapeTimeMinutes, chosenRaidReductionPercent));
|
const newRaidTimeMinutes = Math.floor(this.randomUtil.reduceValueByPercent(baseEscapeTimeMinutes, chosenRaidReductionPercent));
|
||||||
@ -141,8 +142,8 @@ export class RaidTimeAdjustmentService
|
|||||||
// Store time reduction percent in app context so loot gen can pick it up later
|
// Store time reduction percent in app context so loot gen can pick it up later
|
||||||
this.applicationContext.addValue(ContextVariableType.RAID_ADJUSTMENTS,
|
this.applicationContext.addValue(ContextVariableType.RAID_ADJUSTMENTS,
|
||||||
{
|
{
|
||||||
dynamicLootPercent: Math.max(chosenRaidReductionPercent, mapSettings.minDynamicLootPercent),
|
dynamicLootPercent: Math.max(raidTimeRemainingPercent, mapSettings.minDynamicLootPercent),
|
||||||
staticLootPercent: Math.max(chosenRaidReductionPercent, mapSettings.minStaticLootPercent),
|
staticLootPercent: Math.max(raidTimeRemainingPercent, mapSettings.minStaticLootPercent),
|
||||||
simulatedRaidStartSeconds: simulatedRaidStartTimeMinutes * 60
|
simulatedRaidStartSeconds: simulatedRaidStartTimeMinutes * 60
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,7 @@ export class RepairService
|
|||||||
*/
|
*/
|
||||||
public addRepairSkillPoints(sessionId: string, repairDetails: RepairDetails, pmcData: IPmcData): void
|
public addRepairSkillPoints(sessionId: string, repairDetails: RepairDetails, pmcData: IPmcData): void
|
||||||
{
|
{
|
||||||
|
// Handle kit repair of weapon
|
||||||
if (
|
if (
|
||||||
repairDetails.repairedByKit
|
repairDetails.repairedByKit
|
||||||
&& this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON)
|
&& this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON)
|
||||||
@ -148,10 +149,13 @@ export class RepairService
|
|||||||
{
|
{
|
||||||
const skillPoints = this.getWeaponRepairSkillPoints(repairDetails);
|
const skillPoints = this.getWeaponRepairSkillPoints(repairDetails);
|
||||||
|
|
||||||
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.WEAPON_TREATMENT, skillPoints, true);
|
if (skillPoints > 0)
|
||||||
|
{
|
||||||
|
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.WEAPON_TREATMENT, skillPoints, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle kit repairs of armor
|
// Handle kit repair of armor
|
||||||
if (
|
if (
|
||||||
repairDetails.repairedByKit
|
repairDetails.repairedByKit
|
||||||
&& this.itemHelper.isOfBaseclasses(repairDetails.repairedItem._tpl, [BaseClasses.ARMOR, BaseClasses.VEST])
|
&& this.itemHelper.isOfBaseclasses(repairDetails.repairedItem._tpl, [BaseClasses.ARMOR, BaseClasses.VEST])
|
||||||
@ -180,16 +184,25 @@ export class RepairService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle giving INT to player - differs if using kit/trader and weapon vs armor
|
// Handle giving INT to player - differs if using kit/trader and weapon vs armor
|
||||||
let intellectGainedFromRepair: number;
|
const intellectGainedFromRepair = this.getIntellectGainedFromRepair(repairDetails);
|
||||||
|
if (intellectGainedFromRepair > 0)
|
||||||
|
{
|
||||||
|
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.INTELLECT, intellectGainedFromRepair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getIntellectGainedFromRepair(repairDetails: RepairDetails): number
|
||||||
|
{
|
||||||
if (repairDetails.repairedByKit)
|
if (repairDetails.repairedByKit)
|
||||||
{
|
{
|
||||||
|
// Weapons/armor have different multipliers
|
||||||
const intRepairMultiplier =
|
const intRepairMultiplier =
|
||||||
(this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON))
|
(this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON))
|
||||||
? this.repairConfig.repairKitIntellectGainMultiplier.weapon
|
? this.repairConfig.repairKitIntellectGainMultiplier.weapon
|
||||||
: this.repairConfig.repairKitIntellectGainMultiplier.armor;
|
: this.repairConfig.repairKitIntellectGainMultiplier.armor;
|
||||||
|
|
||||||
// limit gain to a max value defined in config.maxIntellectGainPerRepair
|
// Limit gain to a max value defined in config.maxIntellectGainPerRepair
|
||||||
intellectGainedFromRepair = Math.min(
|
return Math.min(
|
||||||
repairDetails.repairPoints * intRepairMultiplier,
|
repairDetails.repairPoints * intRepairMultiplier,
|
||||||
this.repairConfig.maxIntellectGainPerRepair.kit,
|
this.repairConfig.maxIntellectGainPerRepair.kit,
|
||||||
);
|
);
|
||||||
@ -197,13 +210,11 @@ export class RepairService
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Trader repair - Not as accurate as kit, needs data from live
|
// Trader repair - Not as accurate as kit, needs data from live
|
||||||
intellectGainedFromRepair = Math.min(
|
return Math.min(
|
||||||
repairDetails.repairAmount / 10,
|
repairDetails.repairAmount / 10,
|
||||||
this.repairConfig.maxIntellectGainPerRepair.trader,
|
this.repairConfig.maxIntellectGainPerRepair.trader,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.INTELLECT, intellectGainedFromRepair);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -301,6 +301,8 @@ export class SeasonalEventService
|
|||||||
*/
|
*/
|
||||||
protected updateGlobalEvents(sessionId: string, globalConfig: IConfig, eventType: SeasonalEventType): void
|
protected updateGlobalEvents(sessionId: string, globalConfig: IConfig, eventType: SeasonalEventType): void
|
||||||
{
|
{
|
||||||
|
this.logger.success(`${eventType} event is active`);
|
||||||
|
|
||||||
switch (eventType.toLowerCase())
|
switch (eventType.toLowerCase())
|
||||||
{
|
{
|
||||||
case SeasonalEventType.HALLOWEEN.toLowerCase():
|
case SeasonalEventType.HALLOWEEN.toLowerCase():
|
||||||
|
Loading…
Reference in New Issue
Block a user