Refactor: Update various functions with changes from code review

This commit is contained in:
Dev 2023-03-21 14:19:49 +00:00
parent 927273d71d
commit 1fc35881d8
7 changed files with 141 additions and 112 deletions

View File

@ -166,12 +166,12 @@ export class QuestController
/** /**
* Is the quest for the opposite side the player is on * Is the quest for the opposite side the player is on
* @param side player side (usec/bear) * @param playerSide Player side (usec/bear)
* @param questId questId to check * @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)) if (isUsec && this.questConfig.bearOnlyQuests.includes(questId))
{ {
// player is usec and quest is bear only, skip // player is usec and quest is bear only, skip
@ -295,18 +295,17 @@ export class QuestController
*/ */
protected getRepeatableQuestFromProfile(pmcData: IPmcData, acceptedQuest: IAcceptQuestRequestData): IRepeatableQuest protected getRepeatableQuestFromProfile(pmcData: IPmcData, acceptedQuest: IAcceptQuestRequestData): IRepeatableQuest
{ {
let result: IRepeatableQuest; for (const repeatableQuest of pmcData.RepeatableQuests)
for (const repeatable of pmcData.RepeatableQuests)
{ {
result = repeatable.activeQuests.find(x => x._id === acceptedQuest.qid); const result = repeatableQuest.activeQuests.find(x => x._id === acceptedQuest.qid);
if (result) 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; break;
} }
} }
return result; return undefined;
} }
/** /**

View File

@ -324,11 +324,26 @@ export class BotEquipmentModGenerator
*/ */
protected setScopeSpawnChancesToFull(modSpawnChances: ModsChances): void protected setScopeSpawnChancesToFull(modSpawnChances: ModsChances): void
{ {
modSpawnChances["mod_scope"] = 100; if (!modSpawnChances)
modSpawnChances["mod_scope_000"] = 100; {
modSpawnChances["mod_scope_001"] = 100; this.logger.warning("Unable to adjust scope spawn chances as spawn chance object is empty");
modSpawnChances["mod_scope_002"] = 100;
modSpawnChances["mod_scope_003"] = 100; 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[] 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 * never return true for an item that has 0% spawn chance
* @param itemSlot slot the item sits in * @param itemSlot slot the item sits in
* @param modSlot slot the mod sits in * @param modSlot slot the mod sits in

View File

@ -257,23 +257,19 @@ export class BotInventoryGenerator
*/ */
protected getFilteredDynamicModsForItem(itemTpl: string, equipmentBlacklist: EquipmentFilterDetails[]): Record<string, string[]> protected getFilteredDynamicModsForItem(itemTpl: string, equipmentBlacklist: EquipmentFilterDetails[]): Record<string, string[]>
{ {
const results = this.botEquipmentModPoolService.getModsForGearSlot(itemTpl); const modPool = this.botEquipmentModPoolService.getModsForGearSlot(itemTpl);
for (const modSlot in results) for (const modSlot of Object.keys(modPool))
{ {
const blacklistedMods = equipmentBlacklist[0].equipment[modSlot]; const blacklistedMods = equipmentBlacklist[0].equipment[modSlot] || [];
if (!blacklistedMods) const filteredMods = modPool[modSlot].filter(x => !blacklistedMods.includes(x));
{
continue;
}
const filteredMods = results[modSlot].filter(x => !blacklistedMods.includes(x));
if (filteredMods.length > 0) if (filteredMods.length > 0)
{ {
results[modSlot] = filteredMods; modPool[modSlot] = filteredMods;
} }
} }
return results; return modPool;
} }
/** /**

View File

@ -41,64 +41,64 @@ export class BotGeneratorHelper
*/ */
public generateExtraPropertiesForItem(itemTemplate: ITemplateItem, botRole: string = null): { upd?: Upd } public generateExtraPropertiesForItem(itemTemplate: ITemplateItem, botRole: string = null): { upd?: Upd }
{ {
const properties: Upd = {}; const itemProperties: Upd = {};
if (itemTemplate._props.MaxDurability) if (itemTemplate._props.MaxDurability)
{ {
if (itemTemplate._props.weapClass) // Is weapon 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 else if (itemTemplate._props.armorClass) // Is armor
{ {
properties.Repairable = this.generateArmorRepairableProperties(itemTemplate, botRole); itemProperties.Repairable = this.generateArmorRepairableProperties(itemTemplate, botRole);
} }
} }
if (itemTemplate._props.HasHinge) if (itemTemplate._props.HasHinge)
{ {
properties.Togglable = { On: true }; itemProperties.Togglable = { On: true };
} }
if (itemTemplate._props.Foldable) if (itemTemplate._props.Foldable)
{ {
properties.Foldable = { Folded: false }; itemProperties.Foldable = { Folded: false };
} }
if (itemTemplate._props.weapFireType?.length) if (itemTemplate._props.weapFireType?.length)
{ {
if (itemTemplate._props.weapFireType.includes("fullauto")) if (itemTemplate._props.weapFireType.includes("fullauto"))
{ {
properties.FireMode = { FireMode: "fullauto" }; itemProperties.FireMode = { FireMode: "fullauto" };
} }
else else
{ {
properties.FireMode = { FireMode: this.randomUtil.getArrayValue(itemTemplate._props.weapFireType) }; itemProperties.FireMode = { FireMode: this.randomUtil.getArrayValue(itemTemplate._props.weapFireType) };
} }
} }
if (itemTemplate._props.MaxHpResource) if (itemTemplate._props.MaxHpResource)
{ {
properties.MedKit = { HpResource: itemTemplate._props.MaxHpResource }; itemProperties.MedKit = { HpResource: itemTemplate._props.MaxHpResource };
} }
if (itemTemplate._props.MaxResource && itemTemplate._props.foodUseTime) 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(<BaseClasses>itemTemplate._parent)) if ([BaseClasses.FLASHLIGHT, BaseClasses.TACTICAL_COMBO].includes(<BaseClasses>itemTemplate._parent))
{ {
// Get chance from botconfig for bot type, use 50% if no value found // Get chance from botconfig for bot type, use 50% if no value found
const lightLaserActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "lightLaserIsActiveChancePercent", 50); 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) if (itemTemplate._parent === BaseClasses.NIGHTVISION)
{ {
// Get chance from botconfig for bot type, use 50% if no value found // Get chance from botconfig for bot type, use 50% if no value found
const nvgActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChancePercent", 50); const nvgActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChancePercent", 50);
properties.Togglable = { On: (this.randomUtil.getChance100(nvgActiveChance)) }; itemProperties.Togglable = { On: (this.randomUtil.getChance100(nvgActiveChance)) };
} }
// Togglable face shield // Togglable face shield
@ -106,11 +106,11 @@ export class BotGeneratorHelper
{ {
// Get chance from botconfig for bot type, use 75% if no value found // Get chance from botconfig for bot type, use 75% if no value found
const faceShieldActiveChance = this.getBotEquipmentSettingFromConfig(botRole, "faceShieldIsActiveChancePercent", 75); 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 return Object.keys(itemProperties).length
? { upd: properties } ? { upd: itemProperties }
: {}; : {};
} }

View File

@ -48,6 +48,7 @@ export class BotWeaponGeneratorHelper
} }
else if (parentItem._id === BaseClasses.UBGL) else if (parentItem._id === BaseClasses.UBGL)
{ {
// underbarrel launchers can only have 1 chambered grenade
chamberBulletCount = 1; chamberBulletCount = 1;
} }
else else

View File

@ -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; const maxStackSize = this.databaseServer.getTables().templates.items[itemToSplit._tpl]._props.StackMaxSize;
let count = item.upd.StackObjectsCount; let remainingCount = itemToSplit.upd.StackObjectsCount;
const stacks: Item[] = []; const stacks: Item[] = [];
// If the current count is already equal or less than the max // If the current count is already equal or less than the max
// then just return the item as is. // 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; return stacks;
} }
while (count) while (remainingCount)
{ {
const amount = Math.min(count, maxStack); const amount = Math.min(remainingCount, maxStackSize);
const newStack = this.jsonUtil.clone(item); const newStack = this.jsonUtil.clone(itemToSplit);
newStack._id = this.hashUtil.generate(); newStack._id = this.hashUtil.generate();
newStack.upd.StackObjectsCount = amount; newStack.upd.StackObjectsCount = amount;
count -= amount; remainingCount -= amount;
stacks.push(newStack); stacks.push(newStack);
} }
@ -514,19 +517,19 @@ class ItemHelper
/** /**
* Find Barter items in the inventory * Find Barter items in the inventory
* @param {string} by * @param {string} by tpl or id
* @param {Object} pmcData * @param {Object} pmcData
* @param {string} barterItemId * @param {string} barterItemId
* @returns Array of Item objects * @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) // find required items to take after buying (handles multiple items)
const barterIDs = typeof barterItemId === "string" const barterIDs = typeof barterItemId === "string"
? [barterItemId] ? [barterItemId]
: barterItemId; : barterItemId;
let itemsArray: Item[] = [];
let barterItems: Item[] = [];
for (const barterID of barterIDs) for (const barterID of barterIDs)
{ {
const filterResult = pmcData.Inventory.items.filter(item => const filterResult = pmcData.Inventory.items.filter(item =>
@ -536,10 +539,15 @@ class ItemHelper
: (item._id === barterID); : (item._id === barterID);
}); });
itemsArray = Object.assign(itemsArray, filterResult); barterItems = Object.assign(barterItems, filterResult);
} }
return itemsArray; if (barterItems.length === 0)
{
this.logger.warning(`No items found for barter Id: ${barterIDs}`);
}
return barterItems;
} }
/** /**
@ -800,6 +808,7 @@ class ItemHelper
// Add new stack-size-correct items to ammo box // Add new stack-size-correct items to ammo box
let currentStoredCartridgeCount = 0; let currentStoredCartridgeCount = 0;
// Location in ammoBox cartridges will be placed
let location = 0; let location = 0;
while (currentStoredCartridgeCount < ammoBoxMaxCartridgeCount) while (currentStoredCartridgeCount < ammoBoxMaxCartridgeCount)
{ {
@ -808,7 +817,7 @@ class ItemHelper
? ammoBoxMaxCartridgeCount ? ammoBoxMaxCartridgeCount
: cartridgeMaxStackSize; : cartridgeMaxStackSize;
// Add cartridge item object into items array // Add cartridge item into items array
ammoBox.push(this.createCartridges(ammoBox[0]._id, cartridgeTpl, cartridgeCountToAdd, location)); ammoBox.push(this.createCartridges(ammoBox[0]._id, cartridgeTpl, cartridgeCountToAdd, location));
currentStoredCartridgeCount += cartridgeCountToAdd; currentStoredCartridgeCount += cartridgeCountToAdd;
@ -832,7 +841,7 @@ class ItemHelper
minSizePercent = 0.25 minSizePercent = 0.25
): void ): void
{ {
// no caliber defined, choose one // no caliber defined, choose one at random
if (!caliber) if (!caliber)
{ {
caliber = this.getRandomValidCaliber(magTemplate); caliber = this.getRandomValidCaliber(magTemplate);

View File

@ -60,20 +60,16 @@ export class QuestHelper
/** /**
* Get status of a quest in player profile by its id * Get status of a quest in player profile by its id
* @param pmcData Profile to search * @param pmcData Profile to search
* @param questID Quest id to look up * @param questId Quest id to look up
* @returns QuestStatus enum * @returns QuestStatus enum
*/ */
public getQuestStatus(pmcData: IPmcData, questID: string): QuestStatus public getQuestStatus(pmcData: IPmcData, questId: string): QuestStatus
{ {
for (const quest of pmcData.Quests) const quest = pmcData.Quests.find(q => q.qid === questId);
{
if (quest.qid === questID)
{
return quest.status;
}
}
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[] protected processReward(reward: Reward): Reward[]
{ {
let rewardItems: 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) * 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 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 * @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 rewards with the desired status, flatten out items that have a type of Item
const questRewards = quest.rewards[QuestStatus[status]]
// Iterate over all quest rewards .flatMap((reward: Reward) => reward.type === "Item"
for (const reward of quest.rewards[QuestStatus[state]]) // get string version of QuestStatus ? this.processReward(reward)
{ : []);
// Gather up item rewards
if ("Item" === reward.type)
{
questRewards = questRewards.concat(this.processReward(reward));
}
}
return questRewards; return questRewards;
} }
@ -319,47 +314,52 @@ export class QuestHelper
} }
/** /**
* TODO: what is going on here * Get quests that can be shown to player after starting a quest
* @param acceptedQuestId Quest to add to profile * @param startedQuestId Quest started by player
* @param sessionID Session id * @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); 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( // Quest is accessible to player when the accepted quest passed into param is started
c => // 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 c._parent === "Quest" return x._parent === "Quest"
&& c._props.target === acceptedQuestId && x._props.target === startedQuestId
&& c._props.status[0] === QuestStatus.Started; && x._props.status[0] === QuestStatus.Started;
}); });
// Not found, skip quest
if (!acceptedQuestCondition) if (!acceptedQuestCondition)
{ {
return false; return false;
} }
const profileQuest = profile.Quests.find(x => x.qid === acceptedQuestId); // Include if quest found in profile and is started or ready to hand in
return profileQuest && ([QuestStatus.Started, QuestStatus.AvailableForFinish].includes(profileQuest.status));
return profileQuest && (profileQuest.status === QuestStatus.Started || profileQuest.status === QuestStatus.AvailableForFinish);
}); });
return this.getQuestsWithOnlyLevelRequirementStartCondition(quests); return this.getQuestsWithOnlyLevelRequirementStartCondition(eligibleQuests);
} }
/** /**
* TODO: what is going on here * Get quests that can be shown to player after failing a quest
* @param failedQuestId * @param failedQuestId Id of the quest failed by player
* @param sessionID Session id * @param sessionId Session id
* @returns * @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) => const quests = this.getQuestsFromDb().filter((q) =>
{ {
@ -376,8 +376,6 @@ export class QuestHelper
return false; return false;
} }
const profileQuest = profile.Quests.find(x => x.qid === failedQuestId);
return profileQuest && (profileQuest.status === QuestStatus.Fail); return profileQuest && (profileQuest.status === QuestStatus.Fail);
}); });
@ -430,14 +428,7 @@ export class QuestHelper
const item = pmcData.Inventory.items[inventoryItemIndex]; const item = pmcData.Inventory.items[inventoryItemIndex];
item.upd.StackObjectsCount = newStackSize; item.upd.StackObjectsCount = newStackSize;
output.profileChanges[sessionID].items.change.push({ this.addItemStackSizeChangeIntoEventResponse(output, sessionID, item);
"_id": item._id,
"_tpl": item._tpl,
"parentId": item.parentId,
"slotId": item.slotId,
"location": item.location,
"upd": { "StackObjectsCount": item.upd.StackObjectsCount }
});
} }
else 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 * Get quests, strip all requirement conditions except level
* @param quests quests to process * @param quests quests to process