Merge branch '3.8.1-DEV' of https://dev.sp-tarkov.com/SPT-AKI/Server into 3.9.0-DEV

This commit is contained in:
Dev 2024-04-23 10:03:48 +01:00
commit a4bb5a2f60
14 changed files with 98 additions and 64 deletions

View File

@ -15,7 +15,7 @@ jobs:
- name: Clone - name: Clone
run: | run: |
rm -rf /workspace/SPT-AKI/Build/server rm -rf /workspace/SPT-AKI/Build/server
git clone https://dev.sp-tarkov.com/SPT-AKI/Server.git --branch master /workspace/SPT-AKI/Build/server git clone https://dev.sp-tarkov.com/${GITHUB_REPOSITORY}.git --branch master /workspace/SPT-AKI/Build/server
cd /workspace/SPT-AKI/Build/server cd /workspace/SPT-AKI/Build/server
git checkout ${GITHUB_SHA} git checkout ${GITHUB_SHA}

View File

@ -15,7 +15,7 @@ jobs:
- name: Clone - name: Clone
run: | run: |
rm -rf /workspace/SPT-AKI/Build/server rm -rf /workspace/SPT-AKI/Build/server
git clone https://dev.sp-tarkov.com/SPT-AKI/Server.git --branch master /workspace/SPT-AKI/Build/server git clone https://dev.sp-tarkov.com/${GITHUB_REPOSITORY}.git --branch master /workspace/SPT-AKI/Build/server
cd /workspace/SPT-AKI/Build/server cd /workspace/SPT-AKI/Build/server
git checkout ${GITHUB_SHA} git checkout ${GITHUB_SHA}

View File

@ -15,7 +15,7 @@ jobs:
- name: Clone - name: Clone
run: | run: |
rm -rf /workspace/SPT-AKI/Build/server rm -rf /workspace/SPT-AKI/Build/server
git clone https://dev.sp-tarkov.com/SPT-AKI/Server.git --branch master /workspace/SPT-AKI/Build/server git clone https://dev.sp-tarkov.com/${GITHUB_REPOSITORY}.git --branch master /workspace/SPT-AKI/Build/server
cd /workspace/SPT-AKI/Build/server cd /workspace/SPT-AKI/Build/server
git checkout ${GITHUB_SHA} git checkout ${GITHUB_SHA}

View File

@ -27,7 +27,7 @@
"run:build": "gulp run:build", "run:build": "gulp run:build",
"run:debug": "gulp run:debug", "run:debug": "gulp run:debug",
"run:profiler": "gulp run:profiler", "run:profiler": "gulp run:profiler",
"gen:types": "tsc -p tsconfig.typedef.json", "gen:types": "tsc -p tsconfig.typedef.json --resolveJsonModule",
"gen:docs": "typedoc --options ./typedoc.json --entryPointStrategy expand ./src" "gen:docs": "typedoc --options ./typedoc.json --entryPointStrategy expand ./src"
}, },
"dependencies": { "dependencies": {

View File

@ -1106,12 +1106,12 @@ export class BotEquipmentModGenerator
} }
/** /**
* Log errors if mod is not compatible with slot * Check if mod exists in db + is for a required slot
* @param modToAdd template of mod to check * @param modToAdd Db template of mod to check
* @param slotAddedToTemplate slot the item will be placed in * @param slotAddedToTemplate Slot object the item will be placed as child into
* @param modSlot slot the mod will fill * @param modSlot Slot the mod will fill
* @param parentTemplate template of the mods being added * @param parentTemplate Db template of the mods being added
* @param botRole * @param botRole Bots wildspawntype (assault/pmcBot etc)
* @returns true if valid * @returns true if valid
*/ */
protected isModValidForSlot( protected isModValidForSlot(
@ -1122,31 +1122,31 @@ export class BotEquipmentModGenerator
botRole: string, botRole: string,
): boolean ): boolean
{ {
const modBeingAddedTemplate = modToAdd[1]; const modBeingAddedDbTemplate = modToAdd[1];
// Mod lacks template item // Mod lacks db template object
if (!modBeingAddedTemplate) if (!modBeingAddedDbTemplate)
{ {
this.logger.error( this.logger.error(
this.localisationService.getText("bot-no_item_template_found_when_adding_mod", { this.localisationService.getText("bot-no_item_template_found_when_adding_mod", {
modId: modBeingAddedTemplate._id, modId: modBeingAddedDbTemplate?._id ?? "UNKNOWN",
modSlot: modSlot, modSlot: modSlot,
}), }),
); );
this.logger.debug(`Item -> ${parentTemplate._id}; Slot -> ${modSlot}`); this.logger.debug(`Item -> ${parentTemplate?._id}; Slot -> ${modSlot}`);
return false; return false;
} }
// Mod isn't a valid item // Mod has invalid db item
if (!modToAdd[0]) if (!modToAdd[0])
{ {
// Slot must be filled, show warning // Parent slot must be filled but db object is invalid, show warning and return false
if (slotAddedToTemplate._required) if (slotAddedToTemplate._required)
{ {
this.logger.warning( this.logger.warning(
this.localisationService.getText("bot-unable_to_add_mod_item_invalid", { this.localisationService.getText("bot-unable_to_add_mod_item_invalid", {
itemName: modBeingAddedTemplate._name, itemName: modBeingAddedDbTemplate?._name ?? "UNKNOWN",
modSlot: modSlot, modSlot: modSlot,
parentItemName: parentTemplate._name, parentItemName: parentTemplate._name,
botRole: botRole, botRole: botRole,
@ -1157,6 +1157,7 @@ export class BotEquipmentModGenerator
return false; return false;
} }
// Mod was found in db
return true; return true;
} }

View File

@ -270,13 +270,14 @@ export class PMCLootGenerator
protected gcd(a: number, b: number): number protected gcd(a: number, b: number): number
{ {
while (b !== 0) let x = a;
let y = b;
while (y !== 0)
{ {
const temp = b; const temp = y;
b = a % b; y = x % y;
a = temp; x = temp;
} }
return x;
return a;
} }
} }

View File

@ -37,6 +37,8 @@ export class AssortHelper
flea = false, flea = false,
): ITraderAssort ): ITraderAssort
{ {
let strippedTraderAssorts = traderAssorts;
// Trader assort does not always contain loyal_level_items // Trader assort does not always contain loyal_level_items
if (!traderAssorts.loyal_level_items) if (!traderAssorts.loyal_level_items)
{ {
@ -59,11 +61,11 @@ export class AssortHelper
const questStatusInProfile = this.questHelper.getQuestStatus(pmcProfile, unlockValues.questId); const questStatusInProfile = this.questHelper.getQuestStatus(pmcProfile, unlockValues.questId);
if (!unlockValues.status.includes(questStatusInProfile)) if (!unlockValues.status.includes(questStatusInProfile))
{ {
traderAssorts = this.removeItemFromAssort(traderAssorts, assortId, flea); strippedTraderAssorts = this.removeItemFromAssort(traderAssorts, assortId, flea);
} }
} }
return traderAssorts; return strippedTraderAssorts;
} }
/** /**
@ -108,12 +110,14 @@ export class AssortHelper
*/ */
public stripLockedLoyaltyAssort(pmcProfile: IPmcData, traderId: string, assort: ITraderAssort): ITraderAssort public stripLockedLoyaltyAssort(pmcProfile: IPmcData, traderId: string, assort: ITraderAssort): ITraderAssort
{ {
let strippedAssort = assort;
// Trader assort does not always contain loyal_level_items // Trader assort does not always contain loyal_level_items
if (!assort.loyal_level_items) if (!assort.loyal_level_items)
{ {
this.logger.warning(this.localisationService.getText("assort-missing_loyalty_level_object", traderId)); this.logger.warning(this.localisationService.getText("assort-missing_loyalty_level_object", traderId));
return assort; return strippedAssort;
} }
// Remove items restricted by loyalty levels above those reached by the player // Remove items restricted by loyalty levels above those reached by the player
@ -121,11 +125,11 @@ export class AssortHelper
{ {
if (assort.loyal_level_items[itemId] > pmcProfile.TradersInfo[traderId].loyaltyLevel) if (assort.loyal_level_items[itemId] > pmcProfile.TradersInfo[traderId].loyaltyLevel)
{ {
assort = this.removeItemFromAssort(assort, itemId); strippedAssort = this.removeItemFromAssort(assort, itemId);
} }
} }
return assort; return strippedAssort;
} }
/** /**

View File

@ -1198,6 +1198,7 @@ export class ItemHelper
const cartridgeTpl = this.drawAmmoTpl( const cartridgeTpl = this.drawAmmoTpl(
chosenCaliber, chosenCaliber,
staticAmmoDist, staticAmmoDist,
weapon?._props.defAmmo,
weapon?._props?.Chambers[0]?._props?.filters[0]?.Filter, weapon?._props?.Chambers[0]?._props?.filters[0]?.Filter,
); );
this.fillMagazineWithCartridge(magazine, magTemplate, cartridgeTpl, minSizePercent); this.fillMagazineWithCartridge(magazine, magTemplate, cartridgeTpl, minSizePercent);
@ -1306,12 +1307,14 @@ export class ItemHelper
* Chose a randomly weighted cartridge that fits * Chose a randomly weighted cartridge that fits
* @param caliber Desired caliber * @param caliber Desired caliber
* @param staticAmmoDist Cartridges and thier weights * @param staticAmmoDist Cartridges and thier weights
* @param fallbackCartridgeTpl If a cartridge cannot be found in the above staticAmmoDist param, use this instead
* @param cartridgeWhitelist OPTIONAL whitelist for cartridges * @param cartridgeWhitelist OPTIONAL whitelist for cartridges
* @returns Tpl of cartridge * @returns Tpl of cartridge
*/ */
protected drawAmmoTpl( protected drawAmmoTpl(
caliber: string, caliber: string,
staticAmmoDist: Record<string, IStaticAmmoDetails[]>, staticAmmoDist: Record<string, IStaticAmmoDetails[]>,
fallbackCartridgeTpl: string,
cartridgeWhitelist: string[] = null, cartridgeWhitelist: string[] = null,
): string ): string
{ {
@ -1319,14 +1322,20 @@ export class ItemHelper
const ammos = staticAmmoDist[caliber]; const ammos = staticAmmoDist[caliber];
if (!ammos) if (!ammos)
{ {
this.logger.error(`Missing caliber data for: ${caliber}`); this.logger.error(
`Unable to pick a cartridge for caliber: ${caliber} as staticAmmoDist has no data. using fallback value of ${fallbackCartridgeTpl}`,
);
return fallbackCartridgeTpl;
} }
if (!Array.isArray(ammos)) if (!Array.isArray(ammos))
{ {
this.logger.error( this.logger.error(
`Unable to pick a cartridge for caliber ${caliber}, chosen staticAmmoDist data is not an array: ${ammos}`, `Unable to pick a cartridge for caliber: ${caliber}, the chosen staticAmmoDist data is not an array. Using fallback value of ${fallbackCartridgeTpl}`,
); );
return fallbackCartridgeTpl;
} }
for (const icd of ammos) for (const icd of ammos)
@ -1463,7 +1472,6 @@ export class ItemHelper
const modItemDbDetails = this.getItem(modItemToAdd._tpl)[1]; const modItemDbDetails = this.getItem(modItemToAdd._tpl)[1];
// Include conflicting items of newly added mod in pool to be used for next mod choice // Include conflicting items of newly added mod in pool to be used for next mod choice
// biome-ignore lint/complexity/noForEach: <explanation>
modItemDbDetails._props.ConflictingItems.forEach(incompatibleModTpls.add, incompatibleModTpls); modItemDbDetails._props.ConflictingItems.forEach(incompatibleModTpls.add, incompatibleModTpls);
} }
@ -1472,7 +1480,7 @@ export class ItemHelper
/** /**
* Get a compatible tpl from the array provided where it is not found in the provided incompatible mod tpls parameter * Get a compatible tpl from the array provided where it is not found in the provided incompatible mod tpls parameter
* @param possibleTpls Tpls to randomply choose from * @param possibleTpls Tpls to randomly choose from
* @param incompatibleModTpls Incompatible tpls to not allow * @param incompatibleModTpls Incompatible tpls to not allow
* @returns Chosen tpl or null * @returns Chosen tpl or null
*/ */

View File

@ -692,12 +692,12 @@ export class QuestHelper
*/ */
public getQuestWithOnlyLevelRequirementStartCondition(quest: IQuest): IQuest public getQuestWithOnlyLevelRequirementStartCondition(quest: IQuest): IQuest
{ {
quest = this.jsonUtil.clone(quest); const updatedQuest = this.jsonUtil.clone(quest);
quest.conditions.AvailableForStart = quest.conditions.AvailableForStart.filter((q) => updatedQuest.conditions.AvailableForStart = updatedQuest.conditions.AvailableForStart.filter((q) =>
q.conditionType === "Level" q.conditionType === "Level"
); );
return quest; return updatedQuest;
} }
/** /**
@ -714,14 +714,22 @@ export class QuestHelper
output: IItemEventRouterResponse = null, output: IItemEventRouterResponse = null,
): void ): void
{ {
let updatedOutput = output;
// Prepare response to send back to client // Prepare response to send back to client
if (!output) if (!updatedOutput)
{ {
output = this.eventOutputHolder.getOutput(sessionID); updatedOutput = this.eventOutputHolder.getOutput(sessionID);
} }
this.updateQuestState(pmcData, QuestStatus.Fail, failRequest.qid); this.updateQuestState(pmcData, QuestStatus.Fail, failRequest.qid);
const questRewards = this.applyQuestReward(pmcData, failRequest.qid, QuestStatus.Fail, sessionID, output); const questRewards = this.applyQuestReward(
pmcData,
failRequest.qid,
QuestStatus.Fail,
sessionID,
updatedOutput,
);
// Create a dialog message for completing the quest. // Create a dialog message for completing the quest.
const quest = this.getQuestFromDb(failRequest.qid, pmcData); const quest = this.getQuestFromDb(failRequest.qid, pmcData);
@ -747,7 +755,7 @@ export class QuestHelper
} }
} }
output.profileChanges[sessionID].quests.push(...this.failedUnlocked(failRequest.qid, sessionID)); updatedOutput.profileChanges[sessionID].quests.push(...this.failedUnlocked(failRequest.qid, sessionID));
} }
/** /**

View File

@ -72,7 +72,7 @@ export class TradeHelper
): void ): void
{ {
let offerItems: Item[] = []; let offerItems: Item[] = [];
let buyCallback: { (buyCount: number); }; let buyCallback: (buyCount: number) => void;
if (buyRequestData.tid.toLocaleLowerCase() === "ragfair") if (buyRequestData.tid.toLocaleLowerCase() === "ragfair")
{ {
buyCallback = (buyCount: number) => buyCallback = (buyCount: number) =>

View File

@ -15,7 +15,7 @@ export enum Weapons9x39
AS_VAL = "57c44b372459772d2b39b8ce", AS_VAL = "57c44b372459772d2b39b8ce",
VSS_VINTOREZ = "57838ad32459774a17445cd2", VSS_VINTOREZ = "57838ad32459774a17445cd2",
KBP_9A_91 = "644674a13d52156624001fbc", KBP_9A_91 = "644674a13d52156624001fbc",
VSK_94 = "645e0c6b3b381ede770e1cc9", VSK_94 = "645e0c6b3b381ede770e1cc9",
} }
export enum Weapons762x54R export enum Weapons762x54R
@ -26,9 +26,9 @@ export enum Weapons762x54R
MOSIN_SNIPER = "5ae08f0a5acfc408fb1398a1", MOSIN_SNIPER = "5ae08f0a5acfc408fb1398a1",
SV_98 = "55801eed4bdc2d89578b4588", SV_98 = "55801eed4bdc2d89578b4588",
AVT_40 = "6410733d5dd49d77bd07847e", AVT_40 = "6410733d5dd49d77bd07847e",
SVT_40 = "643ea5b23db6f9f57107d9fd", SVT_40 = "643ea5b23db6f9f57107d9fd",
PKM = "64637076203536ad5600c990", PKM = "64637076203536ad5600c990",
PKP = "64ca3d3954fc657e230529cc", PKP = "64ca3d3954fc657e230529cc",
} }
export enum Weapons762x51 export enum Weapons762x51
@ -68,7 +68,7 @@ export enum Weapons762x39
RD_704 = "628a60ae6b1d481ff772e9c8", RD_704 = "628a60ae6b1d481ff772e9c8",
VPO_136 = "59e6152586f77473dc057aa1", VPO_136 = "59e6152586f77473dc057aa1",
RPD = "6513ef33e06849f06c0957ca", RPD = "6513ef33e06849f06c0957ca",
RPDN = "65268d8ecb944ff1e90ea385", RPDN = "65268d8ecb944ff1e90ea385",
} }
export enum Weapons762x35 export enum Weapons762x35
@ -89,7 +89,7 @@ export enum Weapons556x45
SCARL_FDE = "618428466ef05c2ce828f218", SCARL_FDE = "618428466ef05c2ce828f218",
TX15_DML = "5d43021ca4b9362eab4b5e25", TX15_DML = "5d43021ca4b9362eab4b5e25",
AUG_A1 = "62e7c4fba689e8c9c50dfc38", AUG_A1 = "62e7c4fba689e8c9c50dfc38",
AUG_A3 = "63171672192e68c5460cebc5", AUG_A3 = "63171672192e68c5460cebc5",
} }
export enum Weapons545x39 export enum Weapons545x39

View File

@ -387,8 +387,8 @@ export class FenceService
/** /**
* Choose an item at random and remove it + mods from assorts * Choose an item at random and remove it + mods from assorts
* @param assort Items to remove from * @param assort Trader assort to remove item from
* @param rootItems Assort root items to pick from to remove * @param rootItems Pool of root items to pick from to remove
*/ */
protected removeRandomItemFromAssorts(assort: ITraderAssort, rootItems: Item[]): void protected removeRandomItemFromAssorts(assort: ITraderAssort, rootItems: Item[]): void
{ {
@ -399,24 +399,35 @@ export class FenceService
// Get a random count of the chosen item to remove // Get a random count of the chosen item to remove
const itemCountToRemove = this.randomUtil.getInt(1, stackSize); const itemCountToRemove = this.randomUtil.getInt(1, stackSize);
if (itemCountToRemove > 1 && itemCountToRemove < rootItemToAdjust.upd.StackObjectsCount)
{ // More than 1 + less then full stack const isEntireStackToBeRemoved = itemCountToRemove === stackSize;
// Reduce stack size but keep stack
rootItemToAdjust.upd.StackObjectsCount -= itemCountToRemove; // Partial stack reduction
} if (!isEntireStackToBeRemoved)
else
{ {
// Remove up item + any mods if (!rootItemToAdjust.upd)
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(assort.items, rootItemToAdjust._id);
for (const itemToDelete of itemWithChildren)
{ {
// Delete item from assort items array this.logger.warning(`Fence Item: ${rootItemToAdjust._tpl} lacks a upd object, adding`);
assort.items.splice(assort.items.indexOf(itemToDelete), 1); rootItemToAdjust.upd = {};
} }
delete assort.barter_scheme[rootItemToAdjust._id]; // Reduce stack to at smallest, 1
delete assort.loyal_level_items[rootItemToAdjust._id]; rootItemToAdjust.upd.StackObjectsCount -= Math.max(1, itemCountToRemove);
return;
} }
// Remove item + child mods (if any)
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(assort.items, rootItemToAdjust._id);
for (const itemToDelete of itemWithChildren)
{
// Delete item from assort items array
assort.items.splice(assort.items.indexOf(itemToDelete), 1);
}
// Need to remove item from all areas of trader assort
delete assort.barter_scheme[rootItemToAdjust._id];
delete assort.loyal_level_items[rootItemToAdjust._id];
} }
/** /**

View File

@ -134,7 +134,7 @@ export class DatabaseImporter implements OnLoad
try try
{ {
const finalPath = filePathAndName.replace(this.filepath, "").replace(".json", ""); const finalPath = filePathAndName.replace(this.filepath, "").replace(".json", "");
let tempObject; let tempObject: any;
for (const prop of finalPath.split("/")) for (const prop of finalPath.split("/"))
{ {
if (!tempObject) if (!tempObject)

View File

@ -1020,7 +1020,8 @@ describe("InsuranceController", () =>
const attachments = parentAttachmentsMap.entries().next().value; const attachments = parentAttachmentsMap.entries().next().value;
// Set the dynamicPrice of the first attachment to null. // Set the dynamicPrice of the first attachment to null.
vi.spyOn(insuranceController.ragfairPriceService, "getDynamicItemPrice").mockReturnValue(666).mockReturnValueOnce(null); vi.spyOn(insuranceController.ragfairPriceService, "getDynamicItemPrice").mockReturnValue(666)
.mockReturnValueOnce(null);
// Execute the method. // Execute the method.
const sortedAttachments = insuranceController.sortAttachmentsByPrice(attachments); const sortedAttachments = insuranceController.sortAttachmentsByPrice(attachments);