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
|
||||
|
||||
@ -8,6 +8,6 @@ RUN npm i
|
||||
COPY . .
|
||||
RUN npm run gen:docs
|
||||
|
||||
FROM nginx:latest
|
||||
FROM nginx:stable-alpine3.17
|
||||
|
||||
COPY --from=builder /app/docs /usr/share/nginx/html
|
@ -763,7 +763,9 @@
|
||||
"64e74a2fc2b4f829615ec332",
|
||||
"64e74a274d49d23b2c39d317",
|
||||
"64f09c02b63b74469b6c149f",
|
||||
"64f07f7726cfa02c506f8ac0"
|
||||
"64f07f7726cfa02c506f8ac0",
|
||||
"64f69b4267e11a7c6206e010",
|
||||
"64f5b4f71a5f313cb144c06c"
|
||||
],
|
||||
"laboratory": [
|
||||
"6398a4cfb5992f573c6562b3",
|
||||
|
@ -617,39 +617,39 @@
|
||||
"gifterSettings": [{
|
||||
"map": "bigmap",
|
||||
"zones": "ZoneDormitory,ZoneGasStation,ZoneScavBase",
|
||||
"spawnChance": 15
|
||||
"spawnChance": 45
|
||||
}, {
|
||||
"map": "factory4_day",
|
||||
"zones": "BotZone",
|
||||
"spawnChance": 10
|
||||
"spawnChance": 30
|
||||
}, {
|
||||
"map": "laboratory",
|
||||
"zones": "BotZoneFloor1,BotZoneFloor2",
|
||||
"spawnChance": 15
|
||||
"spawnChance": 35
|
||||
}, {
|
||||
"map": "interchange",
|
||||
"zones": "ZoneCenterBot,ZoneCenter,ZoneOLI,ZoneIDEA,ZoneGoshan,ZoneIDEAPark,ZoneOLIPark",
|
||||
"spawnChance": 15
|
||||
"spawnChance": 45
|
||||
}, {
|
||||
"map": "lighthouse",
|
||||
"zones": "Zone_TreatmentContainers,Zone_Chalet,Zone_RoofContainers,Zone_RoofBeach,Zone_Hellicopter",
|
||||
"spawnChance": 15
|
||||
"spawnChance": 45
|
||||
}, {
|
||||
"map": "rezervbase",
|
||||
"zones": "ZoneRailStrorage,ZoneRailStrorage,ZoneRailStrorage,ZonePTOR1,ZonePTOR2,ZoneBarrack,ZoneBarrack,ZoneBarrack,ZoneSubStorage",
|
||||
"spawnChance": 15
|
||||
"spawnChance": 45
|
||||
}, {
|
||||
"map": "shoreline",
|
||||
"zones": "ZonePort,ZoneGreenHouses,ZoneSanatorium1,ZoneGreenHouses,ZoneSanatorium2,ZoneMeteoStation,ZoneSanatorium1,ZoneSanatorium2",
|
||||
"spawnChance": 15
|
||||
"spawnChance": 45
|
||||
}, {
|
||||
"map": "tarkovstreets",
|
||||
"zones": "ZoneColumn",
|
||||
"spawnChance": 10
|
||||
"spawnChance": 40
|
||||
}, {
|
||||
"map": "woods",
|
||||
"zones": "ZoneScavBase2,ZoneWoodCutter",
|
||||
"spawnChance": 15
|
||||
"spawnChance": 45
|
||||
}
|
||||
],
|
||||
"events": [{
|
||||
|
@ -8777,8 +8777,7 @@
|
||||
"mod_magazine": [
|
||||
"55d4887d4bdc2d962f8b4570",
|
||||
"5c05413a0db834001c390617",
|
||||
"5aaa5dfee5b5b000140293d3",
|
||||
"6241c2c2117ad530666a5108"
|
||||
"5aaa5dfee5b5b000140293d3"
|
||||
],
|
||||
"mod_pistol_grip": [
|
||||
"5c0e2ff6d174af02a1659d4a",
|
||||
|
@ -8774,8 +8774,7 @@
|
||||
"mod_magazine": [
|
||||
"55d4887d4bdc2d962f8b4570",
|
||||
"5c05413a0db834001c390617",
|
||||
"5aaa5dfee5b5b000140293d3",
|
||||
"6241c2c2117ad530666a5108"
|
||||
"5aaa5dfee5b5b000140293d3"
|
||||
],
|
||||
"mod_pistol_grip": [
|
||||
"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);
|
||||
}
|
||||
|
||||
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}`);
|
||||
|
||||
@ -38,12 +39,14 @@ export class BundleCallbacks
|
||||
/**
|
||||
* Handle singleplayer/bundles
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public getBundles(url: string, info: any, sessionID: string): string
|
||||
{
|
||||
const local = this.httpConfig.ip === "127.0.0.1" || this.httpConfig.ip === "localhost";
|
||||
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
|
||||
{
|
||||
return "BUNDLE";
|
||||
|
@ -864,15 +864,21 @@ export class HideoutController
|
||||
globals.config.SkillsSettings.HideoutManagement.SkillPointsPerCraft,
|
||||
true,
|
||||
);
|
||||
// manager Crafting skill
|
||||
// Manager Crafting skill
|
||||
if (craftingExpAmount > 0)
|
||||
{
|
||||
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.CRAFTING, craftingExpAmount);
|
||||
this.profileHelper.addSkillPointsToPlayer(
|
||||
pmcData,
|
||||
SkillTypes.INTELLECT,
|
||||
0.5 * (Math.round(craftingExpAmount / 15)),
|
||||
);
|
||||
|
||||
const intellectAmountToGive = 0.5 * (Math.round(craftingExpAmount / 15));
|
||||
if (intellectAmountToGive > 0)
|
||||
{
|
||||
this.profileHelper.addSkillPointsToPlayer(
|
||||
pmcData,
|
||||
SkillTypes.INTELLECT,
|
||||
intellectAmountToGive,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
area.lastRecipe = request.recipeId;
|
||||
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
|
||||
* are still valid. This is checked by endTime persisted in profile according to the resetTime configured for each repeatable kind (daily, weekly)
|
||||
* in QuestConfig.js
|
||||
* are still valid. This ischecked by endTime persisted in profile accordning to the resetTime configured for each repeatable kind (daily, weekly)
|
||||
* in QuestCondig.js
|
||||
*
|
||||
* 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
|
||||
@ -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)
|
||||
* The new quests generated are again persisted in profile.RepeatableQuests
|
||||
*
|
||||
* @param {string} sessionId Player's session id
|
||||
* @returns {array} array of "repeatableQuestObjects" as described above
|
||||
* @param {string} _info Request from client
|
||||
* @param {string} sessionID Player's session id
|
||||
*
|
||||
* @returns {array} array of "repeatableQuestObjects" as descibed above
|
||||
*/
|
||||
public getClientRepeatableQuests(_info: IEmptyRequestData, sessionID: string): IPmcDataRepeatableQuest[]
|
||||
{
|
||||
@ -322,7 +324,7 @@ export class RepeatableQuestController
|
||||
{
|
||||
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")
|
||||
? { locations: possibleLocations.filter((x) => x !== "laboratory") }
|
||||
: { locations: possibleLocations };
|
||||
@ -397,8 +399,7 @@ export class RepeatableQuestController
|
||||
// Get cost to replace existing quest
|
||||
changeRequirement = this.jsonUtil.clone(currentRepeatablePool.changeRequirement[changeRequest.qid]);
|
||||
delete currentRepeatablePool.changeRequirement[changeRequest.qid];
|
||||
|
||||
// TODO: somehow we need to reduce the questPool by the currently active quests (for all repeatable)
|
||||
// TODO: somehow we need to reduce the questPool by the currently active quests (for all repeatables)
|
||||
|
||||
const repeatableConfig = this.questConfig.repeatableQuests.find((x) =>
|
||||
x.name === currentRepeatablePool.name
|
||||
@ -492,6 +493,7 @@ export class RepeatableQuestController
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newRepeatableQuest;
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ export class LocationGenerator
|
||||
staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
|
||||
): SpawnpointTemplate[]
|
||||
{
|
||||
let staticLootItemCount = 0;
|
||||
const result: SpawnpointTemplate[] = [];
|
||||
const locationId = locationBase.Id.toLowerCase();
|
||||
|
||||
@ -127,11 +128,13 @@ export class LocationGenerator
|
||||
locationId,
|
||||
);
|
||||
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 (
|
||||
!(this.locationConfig.containerRandomisationSettings.enabled
|
||||
&& this.locationConfig.containerRandomisationSettings.maps[locationId])
|
||||
@ -150,8 +153,12 @@ export class LocationGenerator
|
||||
locationId,
|
||||
);
|
||||
result.push(containerWithLoot.template);
|
||||
|
||||
staticLootItemCount += containerWithLoot.template.Items.length;
|
||||
}
|
||||
|
||||
this.logger.success(`A total of ${staticLootItemCount} static items spawned`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -230,9 +237,14 @@ export class LocationGenerator
|
||||
);
|
||||
result.push(containerWithLoot.template);
|
||||
staticContainerCount++;
|
||||
|
||||
staticLootItemCount += containerWithLoot.template.Items.length;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.success(`A total of ${staticLootItemCount} static items spawned`);
|
||||
|
||||
this.logger.success(
|
||||
this.localisationService.getText("location-containers_generated_success", staticContainerCount),
|
||||
);
|
||||
|
@ -307,6 +307,8 @@ export class BotGeneratorHelper
|
||||
slot: equipmentSlot,
|
||||
}),
|
||||
);
|
||||
|
||||
return { incompatible: true, reason: `item: ${tplToCheck} does not exist in the database` };
|
||||
}
|
||||
|
||||
if (!itemToCheck._props)
|
||||
@ -318,6 +320,8 @@ export class BotGeneratorHelper
|
||||
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
|
||||
|
@ -21,6 +21,7 @@ import { SaveServer } from "@spt-aki/servers/SaveServer";
|
||||
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||
import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService";
|
||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||
import { TimeUtil } from "@spt-aki/utils/TimeUtil";
|
||||
import { ProfileHelper } from "./ProfileHelper";
|
||||
|
||||
@injectable()
|
||||
@ -31,6 +32,7 @@ export class InRaidHelper
|
||||
|
||||
constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||
@inject("SaveServer") protected saveServer: SaveServer,
|
||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@ -129,7 +131,6 @@ export class InRaidHelper
|
||||
* Reset a profile to a baseline, used post-raid
|
||||
* Reset points earned during session property
|
||||
* Increment exp
|
||||
* Remove Labs keycard
|
||||
* @param profileData Profile to update
|
||||
* @param saveProgressRequest post raid save data request data
|
||||
* @param sessionID Session id
|
||||
@ -218,7 +219,7 @@ export class InRaidHelper
|
||||
public updatePmcProfileDataPostRaid(pmcData: IPmcData, saveProgressRequest: ISaveProgressRequestData, sessionId: string): void
|
||||
{
|
||||
// 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;
|
||||
|
||||
// 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 pmcData Player profile
|
||||
* @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,
|
||||
pmcData: IPmcData,
|
||||
preRaidQuests: IQuestStatus[],
|
||||
postRaidProfile: IPostRaidPmcData,
|
||||
): void
|
||||
{
|
||||
// TODO: this may break when locked quests are added to profile but player has completed no quests prior to raid
|
||||
if (!preRaidQuests)
|
||||
{
|
||||
// No quests to compare against, skip
|
||||
@ -270,29 +272,45 @@ export class InRaidHelper
|
||||
}
|
||||
|
||||
// Loop over all quests from post-raid profile
|
||||
const newLockedQuests: IQuestStatus[] = [];
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
// Already failed before raid, skip
|
||||
if (preRaidQuest.status === QuestStatus.Fail)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preRaidQuest.status === QuestStatus.Success)
|
||||
// Already completed/failed before raid, skip
|
||||
if ([QuestStatus.Fail, QuestStatus.Success].includes(preRaidQuest.status) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Quest failed inside raid, need to handle
|
||||
// postRaidQuest.status has a weird value, need to do some nasty casting to compare it
|
||||
const postRaidQuestStatus = <string><unknown>postRaidQuest.status;
|
||||
// Quest with time-gate has unlocked
|
||||
if (postRaidQuestStatus === "AvailableAfter" && postRaidQuest.availableAfter <= this.timeUtil.getTimestamp())
|
||||
{
|
||||
// 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")
|
||||
{
|
||||
// Send failed message
|
||||
@ -347,6 +365,34 @@ export class InRaidHelper
|
||||
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));
|
||||
});
|
||||
|
||||
for (const item of itemsToRemovePropertyFrom)
|
||||
itemsToRemovePropertyFrom.forEach((item) =>
|
||||
{
|
||||
delete item.upd.SpawnedInSession;
|
||||
}
|
||||
});
|
||||
|
||||
return postRaidProfile;
|
||||
}
|
||||
@ -493,10 +539,11 @@ export class InRaidHelper
|
||||
{
|
||||
// Get inventory item ids to remove from players profile
|
||||
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
|
||||
pmcData.Inventory.fastPanel = {};
|
||||
@ -510,25 +557,25 @@ export class InRaidHelper
|
||||
protected getInventoryItemsLostOnDeath(pmcProfile: IPmcData): Item[]
|
||||
{
|
||||
const inventoryItems = pmcProfile.Inventory.items ?? [];
|
||||
const equipment = pmcProfile?.Inventory?.equipment;
|
||||
const questRaidItems = pmcProfile?.Inventory?.questRaidItems;
|
||||
const equipmentRootId = pmcProfile?.Inventory?.equipment;
|
||||
const questRaidItemContainerId = pmcProfile?.Inventory?.questRaidItems;
|
||||
|
||||
return inventoryItems.filter((x) =>
|
||||
return inventoryItems.filter((item) =>
|
||||
{
|
||||
// Keep items flagged as kept after death
|
||||
if (this.isItemKeptAfterDeath(pmcProfile, x))
|
||||
if (this.isItemKeptAfterDeath(pmcProfile, item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove normal items or quest raid items
|
||||
if (x.parentId === equipment || x.parentId === questRaidItems)
|
||||
if (item.parentId === equipmentRootId || item.parentId === questRaidItemContainerId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pocket items are not lost on death
|
||||
if (x.slotId.startsWith("pocket"))
|
||||
// Pocket items are lost on death
|
||||
if (item.slotId.startsWith("pocket"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -563,7 +610,7 @@ export class InRaidHelper
|
||||
*/
|
||||
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)
|
||||
{
|
||||
return true;
|
||||
@ -583,7 +630,7 @@ export class InRaidHelper
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
@ -623,6 +670,9 @@ export class InRaidHelper
|
||||
"pocket2",
|
||||
"pocket3",
|
||||
"pocket4",
|
||||
"SpecialSlot1",
|
||||
"SpecialSlot2",
|
||||
"SpecialSlot3",
|
||||
];
|
||||
|
||||
let inventoryItems: Item[] = [];
|
||||
|
@ -402,7 +402,7 @@ export class ProfileHelper
|
||||
{
|
||||
if (!pointsToAdd || pointsToAdd < 0)
|
||||
{
|
||||
this.logger.error(
|
||||
this.logger.warning(
|
||||
this.localisationService.getText("player-attempt_to_increment_skill_with_negative_value", skill),
|
||||
);
|
||||
return;
|
||||
@ -432,6 +432,7 @@ export class ProfileHelper
|
||||
}
|
||||
|
||||
profileSkill.Progress += pointsToAdd;
|
||||
profileSkill.Progress = Math.min(profileSkill.Progress, 5100); // Prevent skill from ever going above level 51 (5100)
|
||||
profileSkill.LastAccess = this.timeUtil.getTimestamp();
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ import { TraderAssortHelper } from "@spt-aki/helpers/TraderAssortHelper";
|
||||
import { UtilityHelper } from "@spt-aki/helpers/UtilityHelper";
|
||||
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
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 { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
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[] = [];
|
||||
|
||||
// Case: weapon builds
|
||||
if (info.buildCount)
|
||||
if (request.buildCount)
|
||||
{
|
||||
return Object.keys(info.buildItems);
|
||||
return Object.keys(request.buildItems);
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
// Case: category
|
||||
if (info.handbookId)
|
||||
if (request.handbookId)
|
||||
{
|
||||
const handbook = this.getCategoryList(info.handbookId);
|
||||
const handbook = this.getCategoryList(request.handbookId);
|
||||
|
||||
if (result.length)
|
||||
{
|
||||
@ -182,6 +181,12 @@ export class RagfairHelper
|
||||
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
|
||||
{
|
||||
switch (currencyTpl)
|
||||
|
@ -354,6 +354,7 @@ export class RagfairOfferHelper
|
||||
const profileRagfairInfo = this.saveServer.getProfile(sessionID).characters.pmc.RagfairInfo;
|
||||
const index = profileRagfairInfo.offers.findIndex((o) => o._id === offerId);
|
||||
profileRagfairInfo.offers.splice(index, 1);
|
||||
|
||||
// Also delete from ragfair
|
||||
this.ragfairOfferService.removeOfferById(offerId);
|
||||
}
|
||||
@ -436,15 +437,14 @@ export class RagfairOfferHelper
|
||||
};
|
||||
|
||||
const stacks = this.itemHelper.splitStack(requestedItem);
|
||||
|
||||
for (const item of stacks)
|
||||
{
|
||||
const outItems = [item];
|
||||
|
||||
// TODO - is this code used?, may have been when adding barters to flea was still possible for player
|
||||
if (requirement.onlyFunctional)
|
||||
{
|
||||
const presetItems = this.ragfairServerHelper.getPresetItemsByTpl(item);
|
||||
|
||||
if (presetItems.length)
|
||||
{
|
||||
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 { IQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig";
|
||||
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 { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import { SaveServer } from "@spt-aki/servers/SaveServer";
|
||||
@ -35,6 +36,7 @@ export class RagfairServerHelper
|
||||
protected static goodsReturnedTemplate = "5bdabfe486f7743e1665df6e 0"; // Your item was not sold
|
||||
|
||||
constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||
@ -236,6 +238,11 @@ export class RagfairServerHelper
|
||||
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
|
||||
{
|
||||
if (this.isPlayer(userID))
|
||||
@ -257,16 +264,25 @@ export class RagfairServerHelper
|
||||
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);
|
||||
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[]
|
||||
{
|
||||
const presets = [];
|
||||
|
||||
for (const itemId in this.databaseServer.getTables().globals.ItemPresets)
|
||||
{
|
||||
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
|
||||
* @param item base item
|
||||
* @param preset
|
||||
* Generate new unique ids for child items while preserving hierarchy
|
||||
* @param rootItem Base/primary item of preset
|
||||
* @param preset Primary item + children of primary item
|
||||
* @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 idMappings = {};
|
||||
|
||||
idMappings[oldRootId] = item._id;
|
||||
idMappings[oldRootId] = rootItem._id;
|
||||
|
||||
for (const mod of preset)
|
||||
{
|
||||
@ -314,8 +330,13 @@ export class RagfairServerHelper
|
||||
}
|
||||
}
|
||||
|
||||
// force item's details into first location of presetItems
|
||||
preset[0] = item;
|
||||
// Force item's details into first location of presetItems
|
||||
if (preset[0]._tpl !== rootItem._tpl)
|
||||
{
|
||||
this.logger.warning(`Reassigning root item from ${preset[0]._tpl} to ${rootItem._tpl}`);
|
||||
}
|
||||
|
||||
preset[0] = rootItem;
|
||||
|
||||
return preset;
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ export class RepairHelper
|
||||
): void
|
||||
{
|
||||
this.logger.debug(`Adding ${amountToRepair} to ${itemToRepairDetails._name} using kit: ${useRepairKit}`);
|
||||
|
||||
const itemMaxDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.MaxDurability);
|
||||
const itemCurrentDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.Durability);
|
||||
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(
|
||||
armorMaterial: string,
|
||||
isRepairKit: boolean,
|
||||
@ -112,6 +121,7 @@ export class RepairHelper
|
||||
traderQualityMultipler: number,
|
||||
): number
|
||||
{
|
||||
// Degradation value is based on the armor material
|
||||
const armorMaterialSettings = this.databaseServer.getTables().globals.config.ArmorMaterials[armorMaterial];
|
||||
|
||||
const minMultiplier = isRepairKit
|
||||
@ -128,6 +138,14 @@ export class RepairHelper
|
||||
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(
|
||||
itemProps: Props,
|
||||
isRepairKit: boolean,
|
||||
@ -149,19 +167,4 @@ export class RepairHelper
|
||||
|
||||
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)
|
||||
{}
|
||||
|
||||
/**
|
||||
* 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[]
|
||||
{
|
||||
const secureContainer = items.find((x) => x.slotId === "SecuredContainer");
|
||||
|
@ -120,7 +120,7 @@ export class TradeHelper
|
||||
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);
|
||||
@ -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
|
||||
{
|
||||
if ((assortBeingPurchased.upd.BuyRestrictionCurrent + count) > assortBeingPurchased.upd?.BuyRestrictionMax)
|
||||
{
|
||||
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
|
||||
{
|
||||
// Edge case for Lightkeeper, 0 standing means seeing `Make Amends - Buyout` quest
|
||||
|
@ -18,6 +18,11 @@ export class WeightedRandomHelper
|
||||
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
|
||||
{
|
||||
const itemKeys = Object.keys(itemArray);
|
||||
|
@ -146,10 +146,10 @@ export class PreAkiModLoader implements IModLoader
|
||||
const modOrder = this.vfs.readFile(this.modOrderPath, { encoding: "utf8" });
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
@ -213,10 +213,11 @@ export class PreAkiModLoader implements IModLoader
|
||||
validMods.sort((prev, next) => this.sortMods(prev, next, missingFromOrderJSON));
|
||||
|
||||
// log the missing mods from order.json
|
||||
for (const missingMod of Object.keys(missingFromOrderJSON))
|
||||
{
|
||||
this.logger.debug(this.localisationService.getText("modloader-mod_order_missing_from_json", missingMod));
|
||||
}
|
||||
Object.keys(missingFromOrderJSON).forEach((
|
||||
missingMod,
|
||||
) => (this.logger.debug(
|
||||
this.localisationService.getText("modloader-mod_order_missing_from_json", missingMod),
|
||||
)));
|
||||
|
||||
// add mods
|
||||
for (const mod of validMods)
|
||||
@ -428,9 +429,10 @@ export class PreAkiModLoader implements IModLoader
|
||||
public sortModsLoadOrder(): string[]
|
||||
{
|
||||
// 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
|
||||
{
|
||||
@ -660,14 +662,15 @@ export class PreAkiModLoader implements IModLoader
|
||||
}
|
||||
|
||||
// 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));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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"];
|
||||
let issue = false;
|
||||
|
||||
|
@ -196,14 +196,14 @@ export class EventOutputHolder
|
||||
for (const productionKey in productions)
|
||||
{
|
||||
const production = productions[productionKey];
|
||||
if (production.sptIsComplete && production.sptIsContinuous)
|
||||
if (production?.sptIsComplete && production?.sptIsContinuous)
|
||||
{
|
||||
// Water collector / Bitcoin etc
|
||||
production.sptIsComplete = false;
|
||||
production.Progress = 0;
|
||||
production.StartTimestamp = this.timeUtil.getTimestamp();
|
||||
}
|
||||
else if (!production.inProgress)
|
||||
else if (!production?.inProgress)
|
||||
{
|
||||
// Normal completed craft, delete
|
||||
delete productions[productionKey];
|
||||
|
@ -11,6 +11,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
||||
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
||||
|
||||
@injectable()
|
||||
export class WebSocketServer
|
||||
@ -22,6 +23,7 @@ export class WebSocketServer
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
)
|
||||
{
|
||||
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
||||
@ -92,11 +94,13 @@ export class WebSocketServer
|
||||
// Strip request and break it into sections
|
||||
const splitUrl = req.url.substring(0, req.url.indexOf("?")).split("/");
|
||||
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 msgToLog = this.localisationService.getText("websocket-received_message", sessionID);
|
||||
const msgToLog = this.localisationService.getText("websocket-received_message", playerInfoText);
|
||||
ws.on("message", function message(msg)
|
||||
{
|
||||
logger.info(`${msgToLog} ${msg}`);
|
||||
|
@ -27,7 +27,7 @@ export class HashCacheService
|
||||
// get mod hash file
|
||||
if (!this.modHashes)
|
||||
{ // 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
|
||||
// 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);
|
||||
|
||||
|
@ -129,6 +129,7 @@ export class RaidTimeAdjustmentService
|
||||
const chosenRaidReductionPercent = Number.parseInt(this.weightedRandomHelper.getWeightedValue<string>(
|
||||
mapSettings.reductionPercentWeights,
|
||||
));
|
||||
const raidTimeRemainingPercent = 100 - chosenRaidReductionPercent;
|
||||
|
||||
// How many minutes raid will last
|
||||
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
|
||||
this.applicationContext.addValue(ContextVariableType.RAID_ADJUSTMENTS,
|
||||
{
|
||||
dynamicLootPercent: Math.max(chosenRaidReductionPercent, mapSettings.minDynamicLootPercent),
|
||||
staticLootPercent: Math.max(chosenRaidReductionPercent, mapSettings.minStaticLootPercent),
|
||||
dynamicLootPercent: Math.max(raidTimeRemainingPercent, mapSettings.minDynamicLootPercent),
|
||||
staticLootPercent: Math.max(raidTimeRemainingPercent, mapSettings.minStaticLootPercent),
|
||||
simulatedRaidStartSeconds: simulatedRaidStartTimeMinutes * 60
|
||||
});
|
||||
}
|
||||
|
@ -141,6 +141,7 @@ export class RepairService
|
||||
*/
|
||||
public addRepairSkillPoints(sessionId: string, repairDetails: RepairDetails, pmcData: IPmcData): void
|
||||
{
|
||||
// Handle kit repair of weapon
|
||||
if (
|
||||
repairDetails.repairedByKit
|
||||
&& this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON)
|
||||
@ -148,10 +149,13 @@ export class RepairService
|
||||
{
|
||||
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 (
|
||||
repairDetails.repairedByKit
|
||||
&& 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
|
||||
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)
|
||||
{
|
||||
// Weapons/armor have different multipliers
|
||||
const intRepairMultiplier =
|
||||
(this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON))
|
||||
? this.repairConfig.repairKitIntellectGainMultiplier.weapon
|
||||
: this.repairConfig.repairKitIntellectGainMultiplier.armor;
|
||||
|
||||
// limit gain to a max value defined in config.maxIntellectGainPerRepair
|
||||
intellectGainedFromRepair = Math.min(
|
||||
// Limit gain to a max value defined in config.maxIntellectGainPerRepair
|
||||
return Math.min(
|
||||
repairDetails.repairPoints * intRepairMultiplier,
|
||||
this.repairConfig.maxIntellectGainPerRepair.kit,
|
||||
);
|
||||
@ -197,13 +210,11 @@ export class RepairService
|
||||
else
|
||||
{
|
||||
// Trader repair - Not as accurate as kit, needs data from live
|
||||
intellectGainedFromRepair = Math.min(
|
||||
return Math.min(
|
||||
repairDetails.repairAmount / 10,
|
||||
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
|
||||
{
|
||||
this.logger.success(`${eventType} event is active`);
|
||||
|
||||
switch (eventType.toLowerCase())
|
||||
{
|
||||
case SeasonalEventType.HALLOWEEN.toLowerCase():
|
||||
|
Loading…
Reference in New Issue
Block a user