From 1fc35881d83813fe45691571c361e9da9c45e15d Mon Sep 17 00:00:00 2001 From: Dev Date: Tue, 21 Mar 2023 14:19:49 +0000 Subject: [PATCH] Refactor: Update various functions with changes from code review --- project/src/controllers/QuestController.ts | 17 ++- .../generators/BotEquipmentModGenerator.ts | 27 +++- .../src/generators/BotInventoryGenerator.ts | 16 +-- project/src/helpers/BotGeneratorHelper.ts | 28 ++--- .../src/helpers/BotWeaponGeneratorHelper.ts | 1 + project/src/helpers/ItemHelper.ts | 47 ++++--- project/src/helpers/QuestHelper.ts | 117 ++++++++++-------- 7 files changed, 141 insertions(+), 112 deletions(-) diff --git a/project/src/controllers/QuestController.ts b/project/src/controllers/QuestController.ts index 96ece52c..804b423a 100644 --- a/project/src/controllers/QuestController.ts +++ b/project/src/controllers/QuestController.ts @@ -166,12 +166,12 @@ export class QuestController /** * Is the quest for the opposite side the player is on - * @param side player side (usec/bear) - * @param questId questId to check + * @param playerSide Player side (usec/bear) + * @param questId QuestId to check */ - protected questIsForOtherSide(side: string, questId: string): boolean + protected questIsForOtherSide(playerSide: string, questId: string): boolean { - const isUsec = side.toLowerCase() === "usec"; + const isUsec = playerSide.toLowerCase() === "usec"; if (isUsec && this.questConfig.bearOnlyQuests.includes(questId)) { // player is usec and quest is bear only, skip @@ -295,18 +295,17 @@ export class QuestController */ protected getRepeatableQuestFromProfile(pmcData: IPmcData, acceptedQuest: IAcceptQuestRequestData): IRepeatableQuest { - let result: IRepeatableQuest; - for (const repeatable of pmcData.RepeatableQuests) + for (const repeatableQuest of pmcData.RepeatableQuests) { - result = repeatable.activeQuests.find(x => x._id === acceptedQuest.qid); + const result = repeatableQuest.activeQuests.find(x => x._id === acceptedQuest.qid); if (result) { - this.logger.debug(`Accepted repeatable quest ${acceptedQuest.qid} from ${repeatable.name}`); + this.logger.debug(`Accepted repeatable quest ${acceptedQuest.qid} from ${repeatableQuest.name}`); break; } } - return result; + return undefined; } /** diff --git a/project/src/generators/BotEquipmentModGenerator.ts b/project/src/generators/BotEquipmentModGenerator.ts index 57a1e041..46cdf2e9 100644 --- a/project/src/generators/BotEquipmentModGenerator.ts +++ b/project/src/generators/BotEquipmentModGenerator.ts @@ -324,11 +324,26 @@ export class BotEquipmentModGenerator */ protected setScopeSpawnChancesToFull(modSpawnChances: ModsChances): void { - modSpawnChances["mod_scope"] = 100; - modSpawnChances["mod_scope_000"] = 100; - modSpawnChances["mod_scope_001"] = 100; - modSpawnChances["mod_scope_002"] = 100; - modSpawnChances["mod_scope_003"] = 100; + if (!modSpawnChances) + { + this.logger.warning("Unable to adjust scope spawn chances as spawn chance object is empty"); + + return; + } + + const fullSpawnChancePercent = 100; + const scopeMods = [ + "mod_scope", + "mod_scope_000", + "mod_scope_001", + "mod_scope_002", + "mod_scope_003" + ]; + + for (const modName of scopeMods) + { + modSpawnChances[modName] = fullSpawnChancePercent; + } } protected sortModKeys(unsortedKeys: string[]): string[] @@ -423,7 +438,7 @@ export class BotEquipmentModGenerator } /** - * randomly choose if a mod should be spawned, 100% for required mods OR mod is ammo slot + * Randomly choose if a mod should be spawned, 100% for required mods OR mod is ammo slot * never return true for an item that has 0% spawn chance * @param itemSlot slot the item sits in * @param modSlot slot the mod sits in diff --git a/project/src/generators/BotInventoryGenerator.ts b/project/src/generators/BotInventoryGenerator.ts index 27f4f4ec..576e0eea 100644 --- a/project/src/generators/BotInventoryGenerator.ts +++ b/project/src/generators/BotInventoryGenerator.ts @@ -257,23 +257,19 @@ export class BotInventoryGenerator */ protected getFilteredDynamicModsForItem(itemTpl: string, equipmentBlacklist: EquipmentFilterDetails[]): Record { - const results = this.botEquipmentModPoolService.getModsForGearSlot(itemTpl); - for (const modSlot in results) + const modPool = this.botEquipmentModPoolService.getModsForGearSlot(itemTpl); + for (const modSlot of Object.keys(modPool)) { - const blacklistedMods = equipmentBlacklist[0].equipment[modSlot]; - if (!blacklistedMods) - { - continue; - } - const filteredMods = results[modSlot].filter(x => !blacklistedMods.includes(x)); + const blacklistedMods = equipmentBlacklist[0].equipment[modSlot] || []; + const filteredMods = modPool[modSlot].filter(x => !blacklistedMods.includes(x)); if (filteredMods.length > 0) { - results[modSlot] = filteredMods; + modPool[modSlot] = filteredMods; } } - return results; + return modPool; } /** diff --git a/project/src/helpers/BotGeneratorHelper.ts b/project/src/helpers/BotGeneratorHelper.ts index 9d0f48ae..deee2fb5 100644 --- a/project/src/helpers/BotGeneratorHelper.ts +++ b/project/src/helpers/BotGeneratorHelper.ts @@ -41,64 +41,64 @@ export class BotGeneratorHelper */ public generateExtraPropertiesForItem(itemTemplate: ITemplateItem, botRole: string = null): { upd?: Upd } { - const properties: Upd = {}; + const itemProperties: Upd = {}; if (itemTemplate._props.MaxDurability) { if (itemTemplate._props.weapClass) // Is weapon { - properties.Repairable = this.generateWeaponRepairableProperties(itemTemplate, botRole); + itemProperties.Repairable = this.generateWeaponRepairableProperties(itemTemplate, botRole); } else if (itemTemplate._props.armorClass) // Is armor { - properties.Repairable = this.generateArmorRepairableProperties(itemTemplate, botRole); + itemProperties.Repairable = this.generateArmorRepairableProperties(itemTemplate, botRole); } } if (itemTemplate._props.HasHinge) { - properties.Togglable = { On: true }; + itemProperties.Togglable = { On: true }; } if (itemTemplate._props.Foldable) { - properties.Foldable = { Folded: false }; + itemProperties.Foldable = { Folded: false }; } if (itemTemplate._props.weapFireType?.length) { if (itemTemplate._props.weapFireType.includes("fullauto")) { - properties.FireMode = { FireMode: "fullauto" }; + itemProperties.FireMode = { FireMode: "fullauto" }; } else { - properties.FireMode = { FireMode: this.randomUtil.getArrayValue(itemTemplate._props.weapFireType) }; + itemProperties.FireMode = { FireMode: this.randomUtil.getArrayValue(itemTemplate._props.weapFireType) }; } } if (itemTemplate._props.MaxHpResource) { - properties.MedKit = { HpResource: itemTemplate._props.MaxHpResource }; + itemProperties.MedKit = { HpResource: itemTemplate._props.MaxHpResource }; } if (itemTemplate._props.MaxResource && itemTemplate._props.foodUseTime) { - properties.FoodDrink = { HpPercent: itemTemplate._props.MaxResource }; + itemProperties.FoodDrink = { HpPercent: itemTemplate._props.MaxResource }; } if ([BaseClasses.FLASHLIGHT, BaseClasses.TACTICAL_COMBO].includes(itemTemplate._parent)) { // Get chance from botconfig for bot type, use 50% if no value found const lightLaserActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "lightLaserIsActiveChancePercent", 50); - properties.Light = { IsActive: (this.randomUtil.getChance100(lightLaserActiveChance)), SelectedMode: 0 }; + itemProperties.Light = { IsActive: (this.randomUtil.getChance100(lightLaserActiveChance)), SelectedMode: 0 }; } if (itemTemplate._parent === BaseClasses.NIGHTVISION) { // Get chance from botconfig for bot type, use 50% if no value found const nvgActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChancePercent", 50); - properties.Togglable = { On: (this.randomUtil.getChance100(nvgActiveChance)) }; + itemProperties.Togglable = { On: (this.randomUtil.getChance100(nvgActiveChance)) }; } // Togglable face shield @@ -106,11 +106,11 @@ export class BotGeneratorHelper { // Get chance from botconfig for bot type, use 75% if no value found const faceShieldActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "faceShieldIsActiveChancePercent", 75); - properties.Togglable = { On: (this.randomUtil.getChance100(faceShieldActiveChance)) }; + itemProperties.Togglable = { On: (this.randomUtil.getChance100(faceShieldActiveChance)) }; } - return Object.keys(properties).length - ? { upd: properties } + return Object.keys(itemProperties).length + ? { upd: itemProperties } : {}; } diff --git a/project/src/helpers/BotWeaponGeneratorHelper.ts b/project/src/helpers/BotWeaponGeneratorHelper.ts index 3820f9d6..0051bb91 100644 --- a/project/src/helpers/BotWeaponGeneratorHelper.ts +++ b/project/src/helpers/BotWeaponGeneratorHelper.ts @@ -48,6 +48,7 @@ export class BotWeaponGeneratorHelper } else if (parentItem._id === BaseClasses.UBGL) { + // underbarrel launchers can only have 1 chambered grenade chamberBulletCount = 1; } else diff --git a/project/src/helpers/ItemHelper.ts b/project/src/helpers/ItemHelper.ts index 677283a1..06b75566 100644 --- a/project/src/helpers/ItemHelper.ts +++ b/project/src/helpers/ItemHelper.ts @@ -477,35 +477,38 @@ class ItemHelper } /** - * split item stack if it exceeds StackMaxSize + * split item stack if it exceeds its StackMaxSize property + * @param itemToSplit item being split into smaller stacks + * @returns Array of split items */ - public splitStack(item: Item): Item[] + public splitStack(itemToSplit: Item): Item[] { - if (!(("upd" in item) && ("StackObjectsCount" in item.upd))) + if (!(itemToSplit?.upd?.StackObjectsCount != null)) { - return [item]; + return [itemToSplit]; } - const maxStack = this.databaseServer.getTables().templates.items[item._tpl]._props.StackMaxSize; - let count = item.upd.StackObjectsCount; + const maxStackSize = this.databaseServer.getTables().templates.items[itemToSplit._tpl]._props.StackMaxSize; + let remainingCount = itemToSplit.upd.StackObjectsCount; const stacks: Item[] = []; // If the current count is already equal or less than the max // then just return the item as is. - if (count <= maxStack) + if (remainingCount <= maxStackSize) { - stacks.push(this.jsonUtil.clone(item)); + stacks.push(this.jsonUtil.clone(itemToSplit)); + return stacks; } - while (count) + while (remainingCount) { - const amount = Math.min(count, maxStack); - const newStack = this.jsonUtil.clone(item); + const amount = Math.min(remainingCount, maxStackSize); + const newStack = this.jsonUtil.clone(itemToSplit); newStack._id = this.hashUtil.generate(); newStack.upd.StackObjectsCount = amount; - count -= amount; + remainingCount -= amount; stacks.push(newStack); } @@ -514,19 +517,19 @@ class ItemHelper /** * Find Barter items in the inventory - * @param {string} by + * @param {string} by tpl or id * @param {Object} pmcData * @param {string} barterItemId * @returns Array of Item objects */ - public findBarterItems(by: string, pmcData: IPmcData, barterItemId: string): Item[] + public findBarterItems(by: "tpl" | "id", pmcData: IPmcData, barterItemId: string): Item[] { // find required items to take after buying (handles multiple items) const barterIDs = typeof barterItemId === "string" ? [barterItemId] : barterItemId; - let itemsArray: Item[] = []; + let barterItems: Item[] = []; for (const barterID of barterIDs) { const filterResult = pmcData.Inventory.items.filter(item => @@ -536,10 +539,15 @@ class ItemHelper : (item._id === barterID); }); - itemsArray = Object.assign(itemsArray, filterResult); + barterItems = Object.assign(barterItems, filterResult); + } + + if (barterItems.length === 0) + { + this.logger.warning(`No items found for barter Id: ${barterIDs}`); } - return itemsArray; + return barterItems; } /** @@ -800,6 +808,7 @@ class ItemHelper // Add new stack-size-correct items to ammo box let currentStoredCartridgeCount = 0; + // Location in ammoBox cartridges will be placed let location = 0; while (currentStoredCartridgeCount < ammoBoxMaxCartridgeCount) { @@ -808,7 +817,7 @@ class ItemHelper ? ammoBoxMaxCartridgeCount : cartridgeMaxStackSize; - // Add cartridge item object into items array + // Add cartridge item into items array ammoBox.push(this.createCartridges(ammoBox[0]._id, cartridgeTpl, cartridgeCountToAdd, location)); currentStoredCartridgeCount += cartridgeCountToAdd; @@ -832,7 +841,7 @@ class ItemHelper minSizePercent = 0.25 ): void { - // no caliber defined, choose one + // no caliber defined, choose one at random if (!caliber) { caliber = this.getRandomValidCaliber(magTemplate); diff --git a/project/src/helpers/QuestHelper.ts b/project/src/helpers/QuestHelper.ts index ed47ce8e..41078b59 100644 --- a/project/src/helpers/QuestHelper.ts +++ b/project/src/helpers/QuestHelper.ts @@ -60,20 +60,16 @@ export class QuestHelper /** * Get status of a quest in player profile by its id * @param pmcData Profile to search - * @param questID Quest id to look up + * @param questId Quest id to look up * @returns QuestStatus enum */ - public getQuestStatus(pmcData: IPmcData, questID: string): QuestStatus + public getQuestStatus(pmcData: IPmcData, questId: string): QuestStatus { - for (const quest of pmcData.Quests) - { - if (quest.qid === questID) - { - return quest.status; - } - } + const quest = pmcData.Quests.find(q => q.qid === questId); - return QuestStatus.Locked; + return quest + ? quest.status + : QuestStatus.Locked; } /** @@ -191,6 +187,11 @@ export class QuestHelper } } + /** + * take reward item from quest and set FiR status + fix stack sizes + fix mod Ids + * @param reward Reward item to fix + * @returns Fixed rewards + */ protected processReward(reward: Reward): Reward[] { let rewardItems: Reward[] = []; @@ -252,22 +253,16 @@ export class QuestHelper /** * Gets a flat list of reward items for the given quest at a specific state (e.g. Fail/Success) * @param quest quest to get rewards for - * @param state Quest status that holds the items (Started, Success, Fail) + * @param status Quest status that holds the items (Started, Success, Fail) * @returns array of items with the correct maxStack */ - public getQuestRewardItems(quest: IQuest, state: QuestStatus): Reward[] + public getQuestRewardItems(quest: IQuest, status: QuestStatus): Reward[] { - let questRewards = []; - - // Iterate over all quest rewards - for (const reward of quest.rewards[QuestStatus[state]]) // get string version of QuestStatus - { - // Gather up item rewards - if ("Item" === reward.type) - { - questRewards = questRewards.concat(this.processReward(reward)); - } - } + // Iterate over all rewards with the desired status, flatten out items that have a type of Item + const questRewards = quest.rewards[QuestStatus[status]] + .flatMap((reward: Reward) => reward.type === "Item" + ? this.processReward(reward) + : []); return questRewards; } @@ -319,47 +314,52 @@ export class QuestHelper } /** - * TODO: what is going on here - * @param acceptedQuestId Quest to add to profile + * Get quests that can be shown to player after starting a quest + * @param startedQuestId Quest started by player * @param sessionID Session id - * @returns Array of quests in profile + quest passed in as param + * @returns Quests accessible to player incuding newly unlocked quests now quest (startedQuestId) was started */ - public acceptedUnlocked(acceptedQuestId: string, sessionID: string): IQuest[] + public acceptedUnlocked(startedQuestId: string, sessionID: string): IQuest[] { const profile: IPmcData = this.profileHelper.getPmcProfile(sessionID); + // Get quest acceptance data from profile + const profileQuest = profile.Quests.find(x => x.qid === startedQuestId); - const quests = this.getQuestsFromDb().filter((q) => + // Get quests that + const eligibleQuests = this.getQuestsFromDb().filter((quest) => { - const acceptedQuestCondition = q.conditions.AvailableForStart.find( - c => - { - return c._parent === "Quest" - && c._props.target === acceptedQuestId - && c._props.status[0] === QuestStatus.Started; - }); + // Quest is accessible to player when the accepted quest passed into param is started + // e.g. Quest A passed in, quest B is looped over and has requirement of A to be started, include it + const acceptedQuestCondition = quest.conditions.AvailableForStart.find(x => + { + return x._parent === "Quest" + && x._props.target === startedQuestId + && x._props.status[0] === QuestStatus.Started; + }); + // Not found, skip quest if (!acceptedQuestCondition) { return false; } - const profileQuest = profile.Quests.find(x => x.qid === acceptedQuestId); - - return profileQuest && (profileQuest.status === QuestStatus.Started || profileQuest.status === QuestStatus.AvailableForFinish); + // Include if quest found in profile and is started or ready to hand in + return profileQuest && ([QuestStatus.Started, QuestStatus.AvailableForFinish].includes(profileQuest.status)); }); - return this.getQuestsWithOnlyLevelRequirementStartCondition(quests); + return this.getQuestsWithOnlyLevelRequirementStartCondition(eligibleQuests); } /** - * TODO: what is going on here - * @param failedQuestId - * @param sessionID Session id + * Get quests that can be shown to player after failing a quest + * @param failedQuestId Id of the quest failed by player + * @param sessionId Session id * @returns */ - public failedUnlocked(failedQuestId: string, sessionID: string): IQuest[] + public failedUnlocked(failedQuestId: string, sessionId: string): IQuest[] { - const profile = this.profileHelper.getPmcProfile(sessionID); + const profile = this.profileHelper.getPmcProfile(sessionId); + const profileQuest = profile.Quests.find(x => x.qid === failedQuestId); const quests = this.getQuestsFromDb().filter((q) => { @@ -376,8 +376,6 @@ export class QuestHelper return false; } - const profileQuest = profile.Quests.find(x => x.qid === failedQuestId); - return profileQuest && (profileQuest.status === QuestStatus.Fail); }); @@ -430,14 +428,7 @@ export class QuestHelper const item = pmcData.Inventory.items[inventoryItemIndex]; item.upd.StackObjectsCount = newStackSize; - output.profileChanges[sessionID].items.change.push({ - "_id": item._id, - "_tpl": item._tpl, - "parentId": item.parentId, - "slotId": item.slotId, - "location": item.location, - "upd": { "StackObjectsCount": item.upd.StackObjectsCount } - }); + this.addItemStackSizeChangeIntoEventResponse(output, sessionID, item); } else { @@ -448,6 +439,24 @@ export class QuestHelper } } + /** + * Add item stack change object into output route event response + * @param output Response to add item change event into + * @param sessionId Session id + * @param item Item that was adjusted + */ + protected addItemStackSizeChangeIntoEventResponse(output: IItemEventRouterResponse, sessionId: string, item: Item): void + { + output.profileChanges[sessionId].items.change.push({ + "_id": item._id, + "_tpl": item._tpl, + "parentId": item.parentId, + "slotId": item.slotId, + "location": item.location, + "upd": { "StackObjectsCount": item.upd.StackObjectsCount } + }); + } + /** * Get quests, strip all requirement conditions except level * @param quests quests to process